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, 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
52use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
53use crate::dom::bindings::refcounted::Trusted;
54use crate::dom::bindings::root::MutNullableDom;
55use crate::dom::bindings::trace::NoTrace;
56use crate::dom::clipboardevent::ClipboardEventType;
57use crate::dom::document::FireMouseEventType;
58use crate::dom::document::focus::FocusableArea;
59use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
60#[cfg(feature = "gamepad")]
61use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
62#[cfg(feature = "gamepad")]
63use crate::dom::gamepad::gamepadevent::GamepadEventType;
64use crate::dom::inputevent::HitTestResult;
65use crate::dom::interactive_element_command::InteractiveElementCommand;
66use crate::dom::iterators::ShadowIncluding;
67use crate::dom::keyboardevent::KeyboardEvent;
68use crate::dom::node::{self, Node, NodeTraits};
69use crate::dom::pointerevent::{PointerEvent, PointerId};
70use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
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::drag_data_store::{DragDataStore, Kind, Mode};
77use crate::realms::{enter_auto_realm, enter_realm};
78
79#[derive(Default, JSTraceable, MallocSizeOf)]
89struct ClickCountingInfo {
90 time: Option<Instant>,
91 #[no_trace]
92 point: Option<Point2D<f32, CSSPixel>>,
93 #[no_trace]
94 button: Option<MouseButton>,
95 count: usize,
96}
97
98impl ClickCountingInfo {
99 fn reset_click_count_if_necessary(
100 &mut self,
101 button: MouseButton,
102 point_in_frame: Point2D<f32, CSSPixel>,
103 ) {
104 let (Some(previous_button), Some(previous_point), Some(previous_time)) =
105 (self.button, self.point, self.time)
106 else {
107 assert_eq!(self.count, 0);
108 return;
109 };
110
111 let double_click_timeout =
112 Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
113 let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
114
115 let line = point_in_frame - previous_point;
117 let distance = (line.dot(line) as f64).sqrt();
118 if previous_button != button ||
119 Instant::now().duration_since(previous_time) > double_click_timeout ||
120 distance > double_click_distance_threshold as f64
121 {
122 self.count = 0;
123 self.time = None;
124 self.point = None;
125 }
126 }
127
128 fn increment_click_count(
129 &mut self,
130 button: MouseButton,
131 point: Point2D<f32, CSSPixel>,
132 ) -> usize {
133 self.time = Some(Instant::now());
134 self.point = Some(point);
135 self.button = Some(button);
136 self.count += 1;
137 self.count
138 }
139}
140
141#[derive(JSTraceable, MallocSizeOf)]
145#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
146pub(crate) struct DocumentEventHandler {
147 window: Dom<Window>,
149 #[no_trace]
151 #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
152 pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
153 mouse_move_event_index: DomRefCell<Option<usize>>,
155 #[no_trace]
157 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
158 coalesced_mouse_move_event_ids: DomRefCell<Vec<InputEventId>>,
159 wheel_event_index: DomRefCell<Option<usize>>,
164 #[no_trace]
166 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
167 coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
168 click_counting_info: DomRefCell<ClickCountingInfo>,
170 #[no_trace]
171 last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
172 mouse_buttons_down: Cell<u32>,
176 current_hover_target: MutNullableDom<Element>,
178 current_active_element: MutNullableDom<Element>,
181 most_recently_clicked_element: MutNullableDom<Element>,
183 #[no_trace]
185 most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
186 #[no_trace]
189 current_cursor: Cell<Option<Cursor>>,
190 active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
192 #[no_trace]
194 active_keyboard_modifiers: Cell<Modifiers>,
195 active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
197 next_touch_pointer_id: Cell<i32>,
199 access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
201 pending_pointer_capture: DomRefCell<FxHashMap<i32, Dom<Element>>>,
205 pointer_capture_target: DomRefCell<FxHashMap<i32, Dom<Element>>>,
208}
209
210impl DocumentEventHandler {
211 pub(crate) fn new(window: &Window) -> Self {
212 Self {
213 window: Dom::from_ref(window),
214 pending_input_events: Default::default(),
215 mouse_move_event_index: Default::default(),
216 coalesced_mouse_move_event_ids: Default::default(),
217 wheel_event_index: Default::default(),
218 coalesced_wheel_event_ids: Default::default(),
219 click_counting_info: Default::default(),
220 last_mouse_button_down_point: Default::default(),
221 mouse_buttons_down: Cell::new(0),
222 current_hover_target: Default::default(),
223 current_active_element: Default::default(),
224 most_recently_clicked_element: Default::default(),
225 most_recent_mousemove_point: Default::default(),
226 current_cursor: Default::default(),
227 active_touch_points: Default::default(),
228 active_keyboard_modifiers: Default::default(),
229 active_pointer_ids: Default::default(),
230 next_touch_pointer_id: Cell::new(1),
231 access_key_handlers: Default::default(),
232 pending_pointer_capture: Default::default(),
233 pointer_capture_target: Default::default(),
234 }
235 }
236
237 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
239 let mut pending_input_events = self.pending_input_events.borrow_mut();
240 if matches!(event.event.event, InputEvent::MouseMove(..)) {
241 if let Some(mouse_move_event) = self
243 .mouse_move_event_index
244 .borrow()
245 .and_then(|index| pending_input_events.get_mut(index))
246 {
247 self.coalesced_mouse_move_event_ids
248 .borrow_mut()
249 .push(mouse_move_event.event.id);
250 *mouse_move_event = event;
251 return;
252 }
253
254 *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
255 }
256
257 if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
258 if let Some(existing_constellation_wheel_event) = self
260 .wheel_event_index
261 .borrow()
262 .and_then(|index| pending_input_events.get_mut(index)) &&
263 let InputEvent::Wheel(ref mut existing_wheel_event) =
264 existing_constellation_wheel_event.event.event &&
265 existing_wheel_event.delta.mode == new_wheel_event.delta.mode
266 {
267 self.coalesced_wheel_event_ids
268 .borrow_mut()
269 .push(existing_constellation_wheel_event.event.id);
270 existing_wheel_event.delta.x += new_wheel_event.delta.x;
271 existing_wheel_event.delta.y += new_wheel_event.delta.y;
272 existing_wheel_event.delta.z += new_wheel_event.delta.z;
273 existing_wheel_event.point = new_wheel_event.point;
274 existing_constellation_wheel_event.event.id = event.event.id;
275 return;
276 }
277
278 *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
279 }
280
281 pending_input_events.push(event);
282 }
283
284 pub(crate) fn has_pending_input_events(&self) -> bool {
287 !self.pending_input_events.borrow().is_empty()
288 }
289
290 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
291 #[cfg(target_os = "macos")]
292 {
293 self.active_keyboard_modifiers
294 .get()
295 .contains(Modifiers::META)
296 }
297 #[cfg(not(target_os = "macos"))]
298 {
299 self.active_keyboard_modifiers
300 .get()
301 .contains(Modifiers::CONTROL)
302 }
303 }
304
305 pub(crate) fn handle_pending_input_events(&self, cx: &mut JSContext) {
306 debug_assert!(
307 !self.pending_input_events.borrow().is_empty(),
308 "handle_pending_input_events called with no events"
309 );
310 let _realm = enter_realm(&*self.window);
311
312 *self.mouse_move_event_index.borrow_mut() = None;
314 *self.wheel_event_index.borrow_mut() = None;
315 let pending_input_events = mem::take(&mut *self.pending_input_events.borrow_mut());
316 let mut coalesced_mouse_move_event_ids =
317 mem::take(&mut *self.coalesced_mouse_move_event_ids.borrow_mut());
318 let mut coalesced_wheel_event_ids =
319 mem::take(&mut *self.coalesced_wheel_event_ids.borrow_mut());
320
321 let mut input_event_outcomes = Vec::with_capacity(
322 pending_input_events.len() +
323 coalesced_mouse_move_event_ids.len() +
324 coalesced_wheel_event_ids.len(),
325 );
326 for event in pending_input_events {
332 self.active_keyboard_modifiers
333 .set(event.active_keyboard_modifiers);
334 let result = match event.event.event {
335 InputEvent::MouseButton(mouse_button_event) => {
336 self.handle_native_mouse_button_event(cx, mouse_button_event, &event);
337 InputEventResult::default()
338 },
339 InputEvent::MouseMove(_) => {
340 self.handle_native_mouse_move_event(cx, &event);
341 input_event_outcomes.extend(
342 mem::take(&mut coalesced_mouse_move_event_ids)
343 .into_iter()
344 .map(|id| InputEventOutcome {
345 id,
346 result: InputEventResult::default(),
347 }),
348 );
349 InputEventResult::default()
350 },
351 InputEvent::MouseLeftViewport(mouse_leave_event) => {
352 self.handle_mouse_left_viewport_event(cx, &event, &mouse_leave_event);
353 InputEventResult::default()
354 },
355 InputEvent::Touch(touch_event) => self.handle_touch_event(cx, touch_event, &event),
356 InputEvent::Wheel(wheel_event) => {
357 let result = self.handle_wheel_event(cx, wheel_event, &event);
358 input_event_outcomes.extend(
359 mem::take(&mut coalesced_wheel_event_ids)
360 .into_iter()
361 .map(|id| InputEventOutcome { id, result }),
362 );
363 result
364 },
365 InputEvent::Keyboard(keyboard_event) => {
366 self.handle_keyboard_event(cx, keyboard_event)
367 },
368 InputEvent::Ime(ime_event) => self.handle_ime_event(cx, ime_event),
369 #[cfg(feature = "gamepad")]
370 InputEvent::Gamepad(gamepad_event) => {
371 self.handle_gamepad_event(gamepad_event);
372 InputEventResult::default()
373 },
374 InputEvent::EditingAction(editing_action_event) => {
375 self.handle_editing_action(cx, None, editing_action_event)
376 },
377 };
378
379 input_event_outcomes.push(InputEventOutcome {
380 id: event.event.id,
381 result,
382 });
383 }
384
385 self.notify_embedder_that_events_were_handled(input_event_outcomes);
386 }
387
388 fn notify_embedder_that_events_were_handled(
389 &self,
390 input_event_outcomes: Vec<InputEventOutcome>,
391 ) {
392 let trusted_window = Trusted::new(&*self.window);
395 self.window
396 .as_global_scope()
397 .task_manager()
398 .dom_manipulation_task_source()
399 .queue(task!(notify_webdriver_input_event_completed: move || {
400 let window = trusted_window.root();
401 window.send_to_embedder(
402 EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
403 }));
404 }
405
406 fn target_for_events_following_focus(&self) -> DomRoot<EventTarget> {
410 let document = self.window.Document();
411 match &*document.focus_handler().focused_area() {
412 FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
413 FocusableArea::IFrameViewport { iframe_element, .. } => {
414 DomRoot::from_ref(iframe_element.upcast())
415 },
416 FocusableArea::Viewport => document
417 .GetBody()
418 .map(DomRoot::upcast)
419 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast())),
420 }
421 }
422
423 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
424 if cursor == self.current_cursor.get() {
425 return;
426 }
427 self.current_cursor.set(cursor);
428 self.window.send_to_embedder(EmbedderMsg::SetCursor(
429 self.window.webview_id(),
430 cursor.unwrap_or_default(),
431 ));
432 }
433
434 fn handle_mouse_left_viewport_event(
435 &self,
436 cx: &mut JSContext,
437 input_event: &ConstellationInputEvent,
438 mouse_leave_event: &MouseLeftViewportEvent,
439 ) {
440 if let Some(current_hover_target) = self.current_hover_target.get() {
441 let current_hover_target = current_hover_target.upcast::<Node>();
442 for element in current_hover_target
443 .inclusive_ancestors(ShadowIncluding::Yes)
444 .filter_map(DomRoot::downcast::<Element>)
445 {
446 element.set_hover_state(false);
447 }
448
449 if let Some(hit_test_result) = self
450 .most_recent_mousemove_point
451 .get()
452 .and_then(|point| self.window.hit_test_from_point_in_viewport(point))
453 {
454 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
455 cx,
456 &self.window,
457 FireMouseEventType::Out,
458 &hit_test_result,
459 input_event,
460 );
461
462 mouse_out_event
464 .to_pointer_hover_event("pointerout", CanGc::from_cx(cx))
465 .upcast::<Event>()
466 .fire(cx, current_hover_target.upcast());
467
468 mouse_out_event
469 .upcast::<Event>()
470 .fire(cx, current_hover_target.upcast());
471
472 self.handle_mouse_enter_leave_event(
473 cx,
474 DomRoot::from_ref(current_hover_target),
475 None,
476 FireMouseEventType::Leave,
477 &hit_test_result,
478 input_event,
479 );
480 }
481 }
482
483 if !mouse_leave_event.focus_moving_to_another_iframe {
488 self.window
492 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
493 self.set_cursor(None);
494 } else {
495 self.current_cursor.set(None);
496 }
497
498 self.current_hover_target.set(None);
499 self.most_recent_mousemove_point.set(None);
500 }
501
502 fn handle_mouse_enter_leave_event(
503 &self,
504 cx: &mut JSContext,
505 event_target: DomRoot<Node>,
506 related_target: Option<DomRoot<Node>>,
507 event_type: FireMouseEventType,
508 hit_test_result: &HitTestResult,
509 input_event: &ConstellationInputEvent,
510 ) {
511 assert!(matches!(
512 event_type,
513 FireMouseEventType::Enter | FireMouseEventType::Leave
514 ));
515
516 let common_ancestor = match related_target.as_ref() {
517 Some(related_target) => event_target
518 .common_ancestor_in_flat_tree(related_target)
519 .unwrap_or_else(|| DomRoot::from_ref(&*event_target)),
520 None => DomRoot::from_ref(&*event_target),
521 };
522
523 let mut targets = vec![];
526 let mut current = Some(event_target);
527 while let Some(node) = current {
528 if node == common_ancestor {
529 break;
530 }
531 current = node.parent_in_flat_tree();
532 targets.push(node);
533 }
534
535 if event_type == FireMouseEventType::Enter {
538 targets = targets.into_iter().rev().collect();
539 }
540
541 let pointer_event_name = match event_type {
542 FireMouseEventType::Enter => "pointerenter",
543 FireMouseEventType::Leave => "pointerleave",
544 _ => unreachable!(),
545 };
546
547 for target in targets {
548 let mouse_event = MouseEvent::new_for_platform_motion_event(
549 cx,
550 &self.window,
551 event_type,
552 hit_test_result,
553 input_event,
554 );
555 mouse_event
556 .upcast::<Event>()
557 .set_related_target(related_target.as_ref().map(|target| target.upcast()));
558
559 mouse_event
561 .to_pointer_hover_event(pointer_event_name, CanGc::from_cx(cx))
562 .upcast::<Event>()
563 .fire(cx, target.upcast());
564
565 mouse_event.upcast::<Event>().fire(cx, target.upcast());
567 }
568 }
569
570 fn handle_native_mouse_move_event(
572 &self,
573 cx: &mut JSContext,
574 input_event: &ConstellationInputEvent,
575 ) {
576 let pointer_id = PointerId::Mouse as i32;
579 let released_disconnected =
580 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
581
582 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
587 return;
588 };
589
590 let old_mouse_move_point = self
591 .most_recent_mousemove_point
592 .replace(Some(hit_test_result.point_in_frame));
593 if old_mouse_move_point == Some(hit_test_result.point_in_frame) {
594 return;
595 }
596
597 self.set_cursor(Some(hit_test_result.cursor));
599
600 let Some(new_target) = hit_test_result
601 .node
602 .inclusive_ancestors(ShadowIncluding::Yes)
603 .find_map(DomRoot::downcast::<Element>)
604 else {
605 return;
606 };
607
608 let capture_is_active = self.get_pointer_capture_target(pointer_id).is_some();
609 let old_hover_target = self.current_hover_target.get();
610 let target_has_changed = old_hover_target
611 .as_ref()
612 .is_none_or(|old_target| *old_target != new_target);
613
614 if target_has_changed {
617 if let Some(old_target) = self.current_hover_target.get() {
619 let old_target_is_ancestor_of_new_target = old_target
620 .upcast::<Node>()
621 .is_ancestor_of(new_target.upcast::<Node>());
622
623 if !old_target_is_ancestor_of_new_target {
626 for element in old_target
627 .upcast::<Node>()
628 .inclusive_ancestors(ShadowIncluding::Yes)
629 .filter_map(DomRoot::downcast::<Element>)
630 {
631 element.set_hover_state(false);
632 }
633 }
634
635 if !capture_is_active {
636 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
637 cx,
638 &self.window,
639 FireMouseEventType::Out,
640 &hit_test_result,
641 input_event,
642 );
643 mouse_out_event
644 .upcast::<Event>()
645 .set_related_target(Some(new_target.upcast()));
646
647 mouse_out_event
649 .to_pointer_hover_event("pointerout", CanGc::from_cx(cx))
650 .upcast::<Event>()
651 .fire(cx, old_target.upcast());
652
653 mouse_out_event
654 .upcast::<Event>()
655 .fire(cx, old_target.upcast());
656
657 if !old_target_is_ancestor_of_new_target {
658 let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
659 let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
660 self.handle_mouse_enter_leave_event(
661 cx,
662 event_target,
663 moving_into,
664 FireMouseEventType::Leave,
665 &hit_test_result,
666 input_event,
667 );
668 }
669 }
670 }
671
672 for element in new_target
674 .upcast::<Node>()
675 .inclusive_ancestors(ShadowIncluding::Yes)
676 .filter_map(DomRoot::downcast::<Element>)
677 {
678 element.set_hover_state(true);
679 }
680
681 if !capture_is_active {
682 let mouse_over_event = MouseEvent::new_for_platform_motion_event(
683 cx,
684 &self.window,
685 FireMouseEventType::Over,
686 &hit_test_result,
687 input_event,
688 );
689 mouse_over_event
690 .upcast::<Event>()
691 .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
692
693 mouse_over_event
695 .to_pointer_hover_event("pointerover", CanGc::from_cx(cx))
696 .upcast::<Event>()
697 .dispatch(cx, new_target.upcast(), false);
698
699 mouse_over_event
700 .upcast::<Event>()
701 .dispatch(cx, new_target.upcast(), false);
702
703 let moving_from = old_hover_target
704 .map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
705 let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
706 self.handle_mouse_enter_leave_event(
707 cx,
708 event_target,
709 moving_from,
710 FireMouseEventType::Enter,
711 &hit_test_result,
712 input_event,
713 );
714 }
715 }
716
717 let mouse_event = MouseEvent::new_for_platform_motion_event(
720 cx,
721 &self.window,
722 FireMouseEventType::Move,
723 &hit_test_result,
724 input_event,
725 );
726
727 let pointer_target = self
732 .get_pointer_capture_target(pointer_id)
733 .map(DomRoot::upcast::<EventTarget>)
734 .unwrap_or_else(|| DomRoot::from_ref(new_target.upcast::<EventTarget>()));
735
736 let pointer_event =
737 mouse_event.to_pointer_event(Atom::from("pointermove"), CanGc::from_cx(cx));
738 pointer_event.upcast::<Event>().set_composed(true);
739 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
740
741 if !released_disconnected {
745 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
746 }
747
748 mouse_event.upcast::<Event>().fire(cx, &pointer_target);
750
751 self.update_current_hover_target_and_status(Some(new_target));
752 }
753
754 fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
755 let current_hover_target = self.current_hover_target.get();
756 if current_hover_target == new_hover_target {
757 return;
758 }
759
760 let previous_hover_target = self.current_hover_target.get();
761 self.current_hover_target.set(new_hover_target.as_deref());
762
763 if let Some(target) = self.current_hover_target.get() &&
766 let Some(anchor) = target
767 .upcast::<Node>()
768 .inclusive_ancestors(ShadowIncluding::Yes)
769 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
770 {
771 let status = anchor
772 .full_href_url_for_user_interface()
773 .map(|url| url.to_string());
774 self.window
775 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
776 return;
777 }
778
779 if previous_hover_target.is_none_or(|previous_hover_target| {
784 previous_hover_target
785 .upcast::<Node>()
786 .inclusive_ancestors(ShadowIncluding::Yes)
787 .any(|node| node.is::<HTMLAnchorElement>())
788 }) {
789 self.window
790 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
791 }
792 }
793
794 pub(crate) fn handle_refresh_cursor(&self) {
795 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
796 return;
797 };
798
799 let Some(hit_test_result) = self
800 .window
801 .hit_test_from_point_in_viewport(most_recent_mousemove_point)
802 else {
803 return;
804 };
805
806 self.set_cursor(Some(hit_test_result.cursor));
807 }
808
809 fn set_active_element(&self, original_target: &Element) {
810 let find_element_for_activation = |element: &Element| {
811 let node: &Node = element.upcast();
812 if node.is_in_ua_widget() &&
813 let Some(containing_shadow_root) = node.containing_shadow_root()
814 {
815 return containing_shadow_root.Host();
816 }
817
818 if node.type_id() ==
820 NodeTypeId::Element(ElementTypeId::HTMLElement(
821 HTMLElementTypeId::HTMLLabelElement,
822 ))
823 {
824 let label = element.downcast::<HTMLLabelElement>().unwrap();
825 if let Some(control) = label.GetControl() {
826 return DomRoot::from_ref(control.upcast::<Element>());
827 }
828 }
829
830 DomRoot::from_ref(element)
831 };
832 let element_for_activation = find_element_for_activation(original_target);
833
834 if let Some(currently_active_element) = self.current_active_element.get() {
837 if currently_active_element == element_for_activation {
838 return;
839 }
840 self.unset_active_element();
841 }
842
843 element_for_activation.set_active_state(true);
844 self.current_active_element
845 .set(Some(&*element_for_activation));
846 }
847
848 fn unset_active_element(&self) {
849 if let Some(active_element) = self.current_active_element.take() {
850 active_element.set_active_state(false);
851 }
852 }
853
854 fn handle_native_mouse_button_event(
857 &self,
858 cx: &mut JSContext,
859 event: MouseButtonEvent,
860 input_event: &ConstellationInputEvent,
861 ) {
862 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
864 return;
865 };
866
867 debug!(
868 "{:?}: at {:?}",
869 event.action, hit_test_result.point_in_frame
870 );
871
872 let document = self.window.Document();
875 if event.action == MouseButtonAction::Down {
876 document
877 .focus_handler()
878 .set_sequential_focus_navigation_starting_point(&hit_test_result.node);
879 }
880
881 let Some(element) = hit_test_result
882 .node
883 .inclusive_ancestors(ShadowIncluding::Yes)
884 .find_map(DomRoot::downcast::<Element>)
885 else {
886 return;
887 };
888
889 let node = element.upcast::<Node>();
890 debug!("{:?} on {:?}", event.action, node.debug_str());
891
892 if event.button == MouseButton::Left {
897 if event.action == MouseButtonAction::Down {
898 self.set_active_element(&element);
899 }
900 if event.action == MouseButtonAction::Up {
901 self.unset_active_element();
902 }
903 }
904
905 if element.is_actually_disabled() {
909 return;
910 }
911
912 let mouse_event_type = match event.action {
913 embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
914 embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
915 };
916
917 if event.action == MouseButtonAction::Down {
924 self.click_counting_info
925 .borrow_mut()
926 .reset_click_count_if_necessary(event.button, hit_test_result.point_in_frame);
927 }
928
929 let mouse_event = MouseEvent::for_platform_button_event(
930 cx,
931 mouse_event_type,
932 event,
933 input_event.pressed_mouse_buttons,
934 &self.window,
935 &hit_test_result,
936 input_event.active_keyboard_modifiers,
937 self.click_counting_info.borrow().count + 1,
938 );
939
940 match event.action {
941 MouseButtonAction::Down => {
942 self.last_mouse_button_down_point
943 .set(Some(hit_test_result.point_in_frame));
944
945 let mouse_buttons_down = self.mouse_buttons_down.get();
947 let pointer_event_name = if mouse_buttons_down == 0 {
948 "pointerdown".into()
953 } else {
954 "pointermove".into()
960 };
961 let pointer_event =
962 mouse_event.to_pointer_event(pointer_event_name, CanGc::from_cx(cx));
963
964 let pointer_id = PointerId::Mouse as i32;
966
967 let released_disconnected =
969 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
970
971 let pointer_target = self
973 .get_pointer_capture_target(pointer_id)
974 .map(DomRoot::upcast::<EventTarget>)
975 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
976
977 self.mouse_buttons_down.set(mouse_buttons_down + 1);
979
980 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
981
982 if !released_disconnected {
986 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
987 }
988
989 let result = mouse_event
991 .upcast::<Event>()
992 .dispatch(cx, node.upcast(), false);
993
994 if result {
997 document
1001 .focus_handler()
1002 .focus(cx, node.find_click_focusable_area());
1003 }
1004
1005 if let MouseButton::Right = event.button {
1008 self.maybe_show_context_menu(cx, node.upcast(), &hit_test_result, input_event);
1009 }
1010 },
1011 MouseButtonAction::Up => {
1013 let mouse_buttons_down = self.mouse_buttons_down.get();
1015 let pointer_event_name = if mouse_buttons_down == 1 {
1016 "pointerup".into()
1021 } else {
1022 "pointermove".into()
1028 };
1029 let pointer_event =
1030 mouse_event.to_pointer_event(pointer_event_name, CanGc::from_cx(cx));
1031
1032 let pointer_id = PointerId::Mouse as i32;
1034
1035 let released_disconnected =
1037 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1038
1039 let pointer_target = self
1041 .get_pointer_capture_target(pointer_id)
1042 .map(DomRoot::upcast::<EventTarget>)
1043 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1044
1045 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1046
1047 self.mouse_buttons_down
1050 .set(mouse_buttons_down.saturating_sub(1));
1051
1052 if !released_disconnected {
1056 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1057 }
1058
1059 if mouse_buttons_down == 1 {
1061 self.implicit_release_pointer_capture(cx, pointer_id, "mouse", true);
1062 }
1063
1064 mouse_event
1066 .upcast::<Event>()
1067 .dispatch(cx, node.upcast(), false);
1068
1069 self.click_counting_info
1073 .borrow_mut()
1074 .increment_click_count(event.button, hit_test_result.point_in_frame);
1075
1076 self.maybe_trigger_click_for_mouse_button_down_event(
1077 cx,
1078 event,
1079 input_event,
1080 &hit_test_result,
1081 &element,
1082 );
1083 },
1084 }
1085 }
1086
1087 fn maybe_trigger_click_for_mouse_button_down_event(
1090 &self,
1091 cx: &mut JSContext,
1092 event: MouseButtonEvent,
1093 input_event: &ConstellationInputEvent,
1094 hit_test_result: &HitTestResult,
1095 element: &Element,
1096 ) {
1097 if event.button != MouseButton::Left {
1098 return;
1099 }
1100
1101 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
1102 return;
1103 };
1104
1105 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
1106 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
1107 if distance > maximum_click_distance {
1108 return;
1109 }
1110
1111 let element = match hit_test_result.node.find_click_focusable_area() {
1118 FocusableArea::Node { node, .. } => DomRoot::downcast::<Element>(node),
1119 _ => None,
1120 }
1121 .unwrap_or_else(|| DomRoot::from_ref(element));
1122 self.most_recently_clicked_element.set(Some(&*element));
1123
1124 let click_count = self.click_counting_info.borrow().count;
1125 element.set_click_in_progress(true);
1126 MouseEvent::for_platform_button_event(
1127 cx,
1128 atom!("click"),
1129 event,
1130 input_event.pressed_mouse_buttons,
1131 &self.window,
1132 hit_test_result,
1133 input_event.active_keyboard_modifiers,
1134 click_count,
1135 )
1136 .upcast::<Event>()
1137 .dispatch(cx, element.upcast(), false);
1138 element.set_click_in_progress(false);
1139
1140 if click_count.is_multiple_of(2) {
1149 MouseEvent::for_platform_button_event(
1150 cx,
1151 Atom::from("dblclick"),
1152 event,
1153 input_event.pressed_mouse_buttons,
1154 &self.window,
1155 hit_test_result,
1156 input_event.active_keyboard_modifiers,
1157 2,
1158 )
1159 .upcast::<Event>()
1160 .dispatch(cx, element.upcast(), false);
1161 }
1162 }
1163
1164 fn maybe_show_context_menu(
1166 &self,
1167 cx: &mut js::context::JSContext,
1168 target: &EventTarget,
1169 hit_test_result: &HitTestResult,
1170 input_event: &ConstellationInputEvent,
1171 ) {
1172 let menu_event = PointerEvent::new(
1174 &self.window, "contextmenu".into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
1181 hit_test_result.point_in_frame.to_i32(),
1182 hit_test_result
1183 .point_relative_to_initial_containing_block
1184 .to_i32(),
1185 input_event.active_keyboard_modifiers,
1186 2i16, input_event.pressed_mouse_buttons,
1188 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),
1205 );
1206 menu_event.upcast::<Event>().set_composed(true);
1207
1208 let result = menu_event.upcast::<Event>().fire(cx, target);
1210
1211 if result {
1213 self.window
1214 .Document()
1215 .embedder_controls()
1216 .show_context_menu(hit_test_result);
1217 };
1218 }
1219
1220 fn handle_touch_event(
1221 &self,
1222 cx: &mut JSContext,
1223 event: EmbedderTouchEvent,
1224 input_event: &ConstellationInputEvent,
1225 ) -> InputEventResult {
1226 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1228 self.update_active_touch_points_when_early_return(event);
1229 return Default::default();
1230 };
1231
1232 let TouchId(identifier) = event.touch_id;
1233
1234 let Some(element) = hit_test_result
1235 .node
1236 .inclusive_ancestors(ShadowIncluding::Yes)
1237 .find_map(DomRoot::downcast::<Element>)
1238 else {
1239 self.update_active_touch_points_when_early_return(event);
1240 return Default::default();
1241 };
1242
1243 let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1244 let window = &*self.window;
1245
1246 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1247 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1248 let page_x =
1249 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1250 let page_y =
1251 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1252
1253 let pointer_touch = Touch::new(
1255 window,
1256 identifier,
1257 ¤t_target,
1258 client_x,
1259 client_y, client_x,
1261 client_y,
1262 page_x,
1263 page_y,
1264 CanGc::from_cx(cx),
1265 );
1266
1267 let pointer_event_name = match event.event_type {
1269 TouchEventType::Down => "pointerdown",
1270 TouchEventType::Move => "pointermove",
1271 TouchEventType::Up => "pointerup",
1272 TouchEventType::Cancel => "pointercancel",
1273 };
1274
1275 let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1277 let is_primary = self.is_primary_pointer(pointer_id);
1278
1279 if matches!(event.event_type, TouchEventType::Down) {
1282 let pointer_over = pointer_touch.to_pointer_event(
1284 window,
1285 "pointerover",
1286 pointer_id,
1287 is_primary,
1288 input_event.active_keyboard_modifiers,
1289 true, Some(hit_test_result.point_in_node),
1291 CanGc::from_cx(cx),
1292 );
1293 pointer_over.upcast::<Event>().fire(cx, ¤t_target);
1294
1295 self.fire_pointer_event_for_touch(
1297 cx,
1298 &element,
1299 &pointer_touch,
1300 pointer_id,
1301 "pointerenter",
1302 is_primary,
1303 input_event,
1304 &hit_test_result,
1305 );
1306 }
1307
1308 let released_disconnected = if matches!(event.event_type, TouchEventType::Cancel) {
1312 false
1313 } else {
1314 self.release_disconnected_pointer_capture(cx, pointer_id, "touch", is_primary)
1315 };
1316
1317 let pointer_target = self
1319 .get_pointer_capture_target(pointer_id)
1320 .map(DomRoot::upcast::<EventTarget>)
1321 .unwrap_or_else(|| current_target.clone());
1322
1323 let pointer_event = pointer_touch.to_pointer_event(
1324 window,
1325 pointer_event_name,
1326 pointer_id,
1327 is_primary,
1328 input_event.active_keyboard_modifiers,
1329 event.is_cancelable(),
1330 Some(hit_test_result.point_in_node),
1331 CanGc::from_cx(cx),
1332 );
1333 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1334
1335 if !released_disconnected && !matches!(event.event_type, TouchEventType::Cancel) {
1341 self.process_pending_pointer_capture(cx, pointer_id, "touch", is_primary);
1342 }
1343
1344 if matches!(
1347 event.event_type,
1348 TouchEventType::Up | TouchEventType::Cancel
1349 ) {
1350 self.implicit_release_pointer_capture(cx, pointer_id, "touch", is_primary);
1351 }
1352
1353 if matches!(
1356 event.event_type,
1357 TouchEventType::Up | TouchEventType::Cancel
1358 ) {
1359 let pointer_out = pointer_touch.to_pointer_event(
1361 window,
1362 "pointerout",
1363 pointer_id,
1364 is_primary,
1365 input_event.active_keyboard_modifiers,
1366 true, Some(hit_test_result.point_in_node),
1368 CanGc::from_cx(cx),
1369 );
1370 pointer_out.upcast::<Event>().fire(cx, ¤t_target);
1371
1372 self.fire_pointer_event_for_touch(
1374 cx,
1375 &element,
1376 &pointer_touch,
1377 pointer_id,
1378 "pointerleave",
1379 is_primary,
1380 input_event,
1381 &hit_test_result,
1382 );
1383 }
1384
1385 let (touch_dispatch_target, changed_touch) = match event.event_type {
1386 TouchEventType::Down => {
1387 self.active_touch_points
1389 .borrow_mut()
1390 .push(Dom::from_ref(&*pointer_touch));
1391 self.set_active_element(&element);
1392 (current_target, pointer_touch)
1393 },
1394 _ => {
1395 let mut active_touch_points = self.active_touch_points.borrow_mut();
1401 let Some(index) = active_touch_points
1402 .iter()
1403 .position(|point| point.Identifier() == identifier)
1404 else {
1405 warn!("No active touch point for {:?}", event.event_type);
1406 return Default::default();
1407 };
1408 let original_target = active_touch_points[index].Target();
1410
1411 let touch_with_touchstart_target = Touch::new(
1412 window,
1413 identifier,
1414 &original_target,
1415 client_x,
1416 client_y,
1417 client_x,
1418 client_y,
1419 page_x,
1420 page_y,
1421 CanGc::from_cx(cx),
1422 );
1423
1424 match event.event_type {
1426 TouchEventType::Move => {
1427 active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1428 },
1429 TouchEventType::Up | TouchEventType::Cancel => {
1430 active_touch_points.swap_remove(index);
1431 self.remove_pointer_id_for_touch(identifier);
1432 self.unset_active_element();
1433 },
1434 TouchEventType::Down => unreachable!("Should have been handled above"),
1435 }
1436 (original_target, touch_with_touchstart_target)
1437 },
1438 };
1439
1440 rooted_vec!(let mut target_touches);
1441 target_touches.extend(
1442 self.active_touch_points
1443 .borrow()
1444 .iter()
1445 .filter(|touch| touch.Target() == touch_dispatch_target)
1446 .cloned(),
1447 );
1448
1449 let event_name = match event.event_type {
1450 TouchEventType::Down => "touchstart",
1451 TouchEventType::Move => "touchmove",
1452 TouchEventType::Up => "touchend",
1453 TouchEventType::Cancel => "touchcancel",
1454 };
1455
1456 let touch_event = TouchEvent::new(
1457 window,
1458 event_name.into(),
1459 EventBubbles::Bubbles,
1460 EventCancelable::from(event.is_cancelable()),
1461 EventComposed::Composed,
1462 Some(window),
1463 0i32,
1464 &TouchList::new(
1465 window,
1466 self.active_touch_points.borrow().r(),
1467 CanGc::from_cx(cx),
1468 ),
1469 &TouchList::new(window, from_ref(&&*changed_touch), CanGc::from_cx(cx)),
1470 &TouchList::new(window, target_touches.r(), CanGc::from_cx(cx)),
1471 false,
1473 false,
1474 false,
1475 false,
1476 CanGc::from_cx(cx),
1477 );
1478 let event = touch_event.upcast::<Event>();
1479 event.fire(cx, &touch_dispatch_target);
1480 event.flags().into()
1481 }
1482
1483 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1492 match event.event_type {
1493 TouchEventType::Down | TouchEventType::Move => {},
1494 TouchEventType::Up | TouchEventType::Cancel => {
1495 let mut active_touch_points = self.active_touch_points.borrow_mut();
1496 if let Some(index) = active_touch_points
1497 .iter()
1498 .position(|t| t.Identifier() == event.touch_id.0)
1499 {
1500 active_touch_points.swap_remove(index);
1501 self.remove_pointer_id_for_touch(event.touch_id.0);
1502 } else {
1503 warn!(
1504 "Received {:?} for a non-active touch point {}",
1505 event.event_type, event.touch_id.0
1506 );
1507 }
1508 },
1509 }
1510 }
1511
1512 fn handle_keyboard_event(
1514 &self,
1515 cx: &mut JSContext,
1516 keyboard_event: EmbedderKeyboardEvent,
1517 ) -> InputEventResult {
1518 let target = &self.target_for_events_following_focus();
1519 let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1520 cx,
1521 &self.window,
1522 keyboard_event.event.state.event_type().into(),
1523 &keyboard_event.event,
1524 );
1525
1526 let event = keyevent.upcast::<Event>();
1527
1528 event.set_composed(true);
1529
1530 event.fire(cx, target);
1531
1532 let mut flags = event.flags();
1533 if flags.contains(EventFlags::Canceled) {
1534 return flags.into();
1535 }
1536
1537 let is_character_value_key = matches!(
1543 keyboard_event.event.key,
1544 Key::Character(_) | Key::Named(NamedKey::Enter)
1545 );
1546 if keyboard_event.event.state == KeyState::Down &&
1547 is_character_value_key &&
1548 !keyboard_event.event.is_composing
1549 {
1550 let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1552 cx,
1553 &self.window,
1554 atom!("keypress"),
1555 &keyboard_event.event,
1556 );
1557 keypress_event.upcast::<Event>().set_composed(true);
1558 let event = keypress_event.upcast::<Event>();
1559 event.fire(cx, target);
1560 flags = event.flags();
1561 }
1562
1563 flags.into()
1564 }
1565
1566 fn handle_ime_event(&self, cx: &mut JSContext, event: ImeEvent) -> InputEventResult {
1567 let document = self.window.Document();
1568 let composition_event = match event {
1569 ImeEvent::Dismissed => {
1570 document.focus_handler().focus(cx, FocusableArea::Viewport);
1571 return Default::default();
1572 },
1573 ImeEvent::Composition(composition_event) => composition_event,
1574 };
1575
1576 let focused_area = document.focus_handler().focused_area();
1581 let Some(focused_element) = focused_area.element() else {
1582 return Default::default();
1584 };
1585
1586 let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1587 let event = CompositionEvent::new(
1588 &self.window,
1589 composition_event.state.event_type().into(),
1590 true,
1591 cancelable,
1592 Some(&self.window),
1593 0,
1594 DOMString::from(composition_event.data),
1595 CanGc::from_cx(cx),
1596 );
1597
1598 let event = event.upcast::<Event>();
1599 event.fire(cx, focused_element.upcast());
1600 event.flags().into()
1601 }
1602
1603 fn handle_wheel_event(
1604 &self,
1605 cx: &mut JSContext,
1606 event: EmbedderWheelEvent,
1607 input_event: &ConstellationInputEvent,
1608 ) -> InputEventResult {
1609 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1611 return Default::default();
1612 };
1613
1614 let Some(el) = hit_test_result
1615 .node
1616 .inclusive_ancestors(ShadowIncluding::Yes)
1617 .find_map(DomRoot::downcast::<Element>)
1618 else {
1619 return Default::default();
1620 };
1621
1622 let node = el.upcast::<Node>();
1623 debug!(
1624 "wheel: on {:?} at {:?}",
1625 node.debug_str(),
1626 hit_test_result.point_in_frame
1627 );
1628
1629 let dom_event = WheelEvent::new(
1631 &self.window,
1632 "wheel".into(),
1633 EventBubbles::Bubbles,
1634 EventCancelable::Cancelable,
1635 Some(&self.window),
1636 0i32,
1637 hit_test_result.point_in_frame.to_i32(),
1638 hit_test_result.point_in_frame.to_i32(),
1639 hit_test_result
1640 .point_relative_to_initial_containing_block
1641 .to_i32(),
1642 input_event.active_keyboard_modifiers,
1643 0i16,
1644 input_event.pressed_mouse_buttons,
1645 None,
1646 None,
1647 Finite::wrap(-event.delta.x),
1652 Finite::wrap(-event.delta.y),
1653 Finite::wrap(-event.delta.z),
1654 event.delta.mode as u32,
1655 CanGc::from_cx(cx),
1656 );
1657
1658 let dom_event = dom_event.upcast::<Event>();
1659 dom_event.set_trusted(true);
1660 dom_event.set_composed(true);
1661 dom_event.fire(cx, node.upcast());
1662
1663 dom_event.flags().into()
1664 }
1665
1666 #[cfg(feature = "gamepad")]
1667 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1668 match gamepad_event {
1669 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1670 self.handle_gamepad_connect(
1671 index.0,
1672 name,
1673 bounds.axis_bounds,
1674 bounds.button_bounds,
1675 supported_haptic_effects,
1676 );
1677 },
1678 EmbedderGamepadEvent::Disconnected(index) => {
1679 self.handle_gamepad_disconnect(index.0);
1680 },
1681 EmbedderGamepadEvent::Updated(index, update_type) => {
1682 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1683 },
1684 };
1685 }
1686
1687 #[cfg(feature = "gamepad")]
1689 fn handle_gamepad_connect(
1690 &self,
1691 _index: usize,
1695 name: String,
1696 axis_bounds: (f64, f64),
1697 button_bounds: (f64, f64),
1698 supported_haptic_effects: GamepadSupportedHapticEffects,
1699 ) {
1700 let trusted_window = Trusted::new(&*self.window);
1704
1705 self.window
1708 .upcast::<GlobalScope>()
1709 .task_manager()
1710 .gamepad_task_source()
1711 .queue(task!(gamepad_connected: move |cx| {
1712 let window = trusted_window.root();
1713
1714 let navigator = window.Navigator();
1717 let selected_index = navigator.select_gamepad_index();
1718 let gamepad = Gamepad::new(
1719 cx,
1720 &window,
1721 selected_index,
1722 name,
1723 "standard".into(),
1724 axis_bounds,
1725 button_bounds,
1726 supported_haptic_effects,
1727 false,
1728 );
1729
1730 navigator.set_gamepad(selected_index as usize, Some(&gamepad));
1732
1733 if navigator.has_gamepad_gesture() {
1735 gamepad.set_exposed(true);
1737 if window.Document().is_fully_active() {
1742 gamepad.notify_event(cx, GamepadEventType::Connected);
1743 }
1744 }
1745 }));
1746 }
1747
1748 #[cfg(feature = "gamepad")]
1750 fn handle_gamepad_disconnect(&self, index: usize) {
1751 let trusted_window = Trusted::new(&*self.window);
1755 self.window
1756 .upcast::<GlobalScope>()
1757 .task_manager()
1758 .gamepad_task_source()
1759 .queue(task!(gamepad_disconnected: move |cx| {
1760 let window = trusted_window.root();
1761 let navigator = window.Navigator();
1762
1763 if let Some(gamepad) = navigator.get_gamepad(index) {
1764 gamepad.update_connected(false);
1766 if gamepad.exposed() && window.Document().is_fully_active() {
1774 gamepad.notify_event(cx, GamepadEventType::Disconnected);
1775 }
1776 }
1777
1778 navigator.set_gamepad(index, None);
1782 navigator.shrink_gamepads_list();
1785 }));
1786 }
1787
1788 #[cfg(feature = "gamepad")]
1790 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1791 let trusted_window = Trusted::new(&*self.window);
1794
1795 self.window
1798 .upcast::<GlobalScope>()
1799 .task_manager()
1800 .gamepad_task_source()
1801 .queue(task!(update_gamepad_state: move || {
1802 let window = trusted_window.root();
1803 let document = window.Document();
1804 document.event_handler().update_gamepad_state(index, update_type);
1805 }));
1806 }
1807
1808 #[cfg(feature = "gamepad")]
1810 fn update_gamepad_state(&self, gamepad_index: usize, update_type: GamepadUpdateType) {
1811 use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
1812 let now = *self.window.Performance().Now();
1815
1816 let navigator = self.window.Navigator();
1819
1820 if let Some(gamepad) = navigator.get_gamepad(gamepad_index) {
1821 gamepad.update_timestamp(now);
1823 match update_type {
1826 GamepadUpdateType::Axis(axis_index, value) => {
1827 gamepad.map_and_normalize_axes(axis_index, value);
1828 },
1829 GamepadUpdateType::Button(button_index, value) => {
1830 gamepad.map_and_normalize_buttons(button_index, value);
1831 },
1832 };
1833 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1838 navigator.set_has_gamepad_gesture(true);
1840 navigator
1842 .get_connected_gamepad()
1843 .iter()
1844 .for_each(|connected_gamepad| {
1845 connected_gamepad.set_exposed(true);
1847 connected_gamepad.update_timestamp(now);
1849 let trusted_gamepad = Trusted::new(&**connected_gamepad);
1856 if self.window.Document().is_fully_active() {
1857 self.window
1858 .upcast::<GlobalScope>()
1859 .task_manager()
1860 .gamepad_task_source()
1861 .queue(task!(fire_gamepad_connected: move |cx| {
1862 let gamepad = trusted_gamepad.root();
1863 gamepad.notify_event(cx, GamepadEventType::Connected);
1864 }));
1865 }
1866 });
1867 }
1868 }
1869 }
1870
1871 pub(crate) fn handle_editing_action(
1873 &self,
1874 cx: &mut JSContext,
1875 element: Option<DomRoot<Element>>,
1876 action: EditingActionEvent,
1877 ) -> InputEventResult {
1878 let clipboard_event_type = match action {
1879 EditingActionEvent::Copy => ClipboardEventType::Copy,
1880 EditingActionEvent::Cut => ClipboardEventType::Cut,
1881 EditingActionEvent::Paste => ClipboardEventType::Paste,
1882 };
1883
1884 let script_triggered = false;
1886
1887 let script_may_access_clipboard = false;
1891
1892 if script_triggered && !script_may_access_clipboard {
1894 return InputEventResult::empty();
1895 }
1896
1897 let clipboard_event = self.fire_clipboard_event(cx, element.clone(), clipboard_event_type);
1899
1900 let event = clipboard_event.upcast::<Event>();
1903 if !event.IsTrusted() {
1904 return event.flags().into();
1905 }
1906
1907 if event.DefaultPrevented() {
1909 let event_type = event.Type();
1910 match_domstring_ascii!(event_type,
1911
1912 "copy" => {
1913 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1916 let drag_data_store =
1917 clipboard_data.data_store().expect("This shouldn't fail");
1918 self.write_content_to_the_clipboard(&drag_data_store);
1919 }
1920 },
1921 "cut" => {
1922 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1925 let drag_data_store =
1926 clipboard_data.data_store().expect("This shouldn't fail");
1927 self.write_content_to_the_clipboard(&drag_data_store);
1928 }
1929
1930 self.fire_clipboard_event(cx, element, ClipboardEventType::Change);
1932 },
1933 "paste" => (),
1937 _ => (),
1938 )
1939 }
1940
1941 event.flags().into()
1944 }
1945
1946 pub(crate) fn fire_clipboard_event(
1948 &self,
1949 cx: &mut JSContext,
1950 target: Option<DomRoot<Element>>,
1951 clipboard_event_type: ClipboardEventType,
1952 ) -> DomRoot<ClipboardEvent> {
1953 let clipboard_event = ClipboardEvent::new(
1954 &self.window,
1955 None,
1956 clipboard_event_type.as_str().into(),
1957 EventBubbles::Bubbles,
1958 EventCancelable::Cancelable,
1959 None,
1960 CanGc::from_cx(cx),
1961 );
1962
1963 let mut drag_data_store = DragDataStore::new();
1966
1967 let trusted = true;
1971
1972 let target = target
1974 .map(DomRoot::upcast)
1975 .unwrap_or_else(|| self.target_for_events_following_focus());
1976
1977 match clipboard_event_type {
1980 ClipboardEventType::Copy | ClipboardEventType::Cut => {
1981 drag_data_store.set_mode(Mode::ReadWrite);
1983 },
1984 ClipboardEventType::Paste => {
1985 let (callback, receiver) =
1986 GenericCallback::new_blocking().expect("Could not create callback");
1987 self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
1988 self.window.webview_id(),
1989 callback,
1990 ));
1991 let text_contents = receiver
1992 .recv()
1993 .map(Result::unwrap_or_default)
1994 .unwrap_or_default();
1995
1996 drag_data_store.set_mode(Mode::ReadOnly);
1998 if trusted {
2000 let data = DOMString::from(text_contents);
2004 let type_ = DOMString::from("text/plain");
2005 let _ = drag_data_store.add(Kind::Text { data, type_ });
2006
2007 }
2013 },
2014 ClipboardEventType::Change => (),
2015 }
2016
2017 let clipboard_event_data = DataTransfer::new(
2019 &self.window,
2020 Rc::new(RefCell::new(Some(drag_data_store))),
2021 CanGc::from_cx(cx),
2022 );
2023
2024 clipboard_event.set_clipboard_data(Some(&clipboard_event_data));
2026
2027 let event = clipboard_event.upcast::<Event>();
2029 event.set_trusted(trusted);
2030
2031 event.set_composed(true);
2033
2034 event.dispatch(cx, &target, false);
2036
2037 DomRoot::from(clipboard_event)
2038 }
2039
2040 fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
2042 if drag_data_store.list_len() > 0 {
2044 self.window
2046 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
2047 for item in drag_data_store.iter_item_list() {
2049 match item {
2050 Kind::Text { data, .. } => {
2051 self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
2055 self.window.webview_id(),
2056 data.to_string(),
2057 ));
2058 },
2059 Kind::File { .. } => {
2060 },
2064 }
2065 }
2066 } else {
2067 if drag_data_store.clear_was_called {
2069 self.window
2071 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
2072 }
2075 }
2076 }
2077
2078 #[expect(unsafe_code)]
2081 pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
2082 let document = self.window.Document();
2084 if scrolled_node.is_root() {
2085 document.handle_viewport_scroll_event();
2086 } else {
2087 let node_id = node_id_from_scroll_id(scrolled_node.0 as usize);
2091 let node = unsafe {
2092 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
2093 };
2094 let Some(element) = node
2095 .inclusive_ancestors(ShadowIncluding::Yes)
2096 .find_map(DomRoot::downcast::<Element>)
2097 else {
2098 return;
2099 };
2100
2101 element.handle_scroll_event();
2102 }
2103 }
2104
2105 pub(crate) fn maybe_dispatch_simulated_click(
2111 &self,
2112 cx: &mut JSContext,
2113 node: &Node,
2114 event: &KeyboardEvent,
2115 ) -> bool {
2116 if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
2117 {
2118 return false;
2119 }
2120
2121 if node
2125 .downcast::<Element>()
2126 .and_then(Element::as_maybe_activatable)
2127 .is_none()
2128 {
2129 return false;
2130 }
2131
2132 node.fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
2133 true
2134 }
2135
2136 pub(crate) fn run_default_keyboard_event_handler(
2137 &self,
2138 cx: &mut js::context::JSContext,
2139 node: &Node,
2140 event: &KeyboardEvent,
2141 ) {
2142 if event.upcast::<Event>().type_() != atom!("keydown") {
2143 return;
2144 }
2145
2146 if self.maybe_dispatch_simulated_click(cx, node, event) {
2147 return;
2148 }
2149
2150 if self.maybe_handle_accesskey(cx, event) {
2151 return;
2152 }
2153
2154 let mut is_space = false;
2155 let scroll = match event.key() {
2156 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
2157 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
2158 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
2159 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
2160 Key::Named(NamedKey::End) => KeyboardScroll::End,
2161 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
2162 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
2163 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
2164 Key::Character(string) if &string == " " => {
2165 is_space = true;
2166 if event.modifiers().contains(Modifiers::SHIFT) {
2167 KeyboardScroll::PageUp
2168 } else {
2169 KeyboardScroll::PageDown
2170 }
2171 },
2172 Key::Named(NamedKey::Tab) => {
2173 self.window
2179 .Document()
2180 .focus_handler()
2181 .sequential_focus_navigation_via_keyboard_event(cx, event);
2182 return;
2183 },
2184 _ => return,
2185 };
2186
2187 if !event.modifiers().is_empty() && !is_space {
2188 return;
2189 }
2190
2191 self.do_keyboard_scroll(cx, scroll);
2192 }
2193
2194 pub(crate) fn do_keyboard_scroll(&self, cx: &mut JSContext, scroll: KeyboardScroll) {
2195 let scroll_axis = match scroll {
2196 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2197 _ => ScrollingBoxAxis::Y,
2198 };
2199
2200 let document = self.window.Document();
2201 let mut scrolling_box = document
2202 .focus_handler()
2203 .focused_area()
2204 .element()
2205 .or(self.most_recently_clicked_element.get().as_deref())
2206 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2207 .unwrap_or_else(|| {
2208 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2209 });
2210
2211 while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2212 if scrolling_box.is_viewport() {
2214 break;
2215 }
2216 let parent = scrolling_box.parent().unwrap_or_else(|| {
2217 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2218 });
2219 scrolling_box = parent;
2220 }
2221
2222 let calculate_current_scroll_offset_and_delta = || {
2223 const LINE_HEIGHT: f32 = 76.0;
2224 const LINE_WIDTH: f32 = 76.0;
2225
2226 let current_scroll_offset = scrolling_box.scroll_position();
2227 (
2228 current_scroll_offset,
2229 match scroll {
2230 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2231 KeyboardScroll::End => Vector2D::new(
2232 0.0,
2233 -current_scroll_offset.y + scrolling_box.content_size().height -
2234 scrolling_box.size().height,
2235 ),
2236 KeyboardScroll::PageDown => {
2237 Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
2238 },
2239 KeyboardScroll::PageUp => {
2240 Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
2241 },
2242 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2243 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2244 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2245 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2246 },
2247 )
2248 };
2249
2250 let parent_pipeline = self.window.parent_info();
2254 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2255 let (_, delta) = calculate_current_scroll_offset_and_delta();
2256 self.window
2257 .paint_api()
2258 .scroll_viewport_by_delta(self.window.webview_id(), delta);
2259 }
2260
2261 if !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2264 assert!(scrolling_box.is_viewport());
2265
2266 let window_proxy = document.window().window_proxy();
2267 if let Some(iframe) = window_proxy.frame_element() {
2268 let iframe_window = iframe.owner_window();
2271 let mut realm = enter_auto_realm(cx, &*iframe_window);
2272 let cx = &mut realm;
2273 iframe_window
2274 .Document()
2275 .event_handler()
2276 .do_keyboard_scroll(cx, scroll);
2277 } else if let Some(parent_pipeline) = parent_pipeline {
2278 document.window().send_to_constellation(
2282 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2283 );
2284 };
2285 return;
2286 }
2287
2288 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2289 scrolling_box.scroll_to(cx, delta + current_scroll_offset, ScrollBehavior::Auto);
2290 }
2291
2292 fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2295 let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2296
2297 if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2298 return pointer_id;
2299 }
2300
2301 let pointer_id = self.next_touch_pointer_id.get();
2302 active_pointer_ids.insert(touch_id, pointer_id);
2303 self.next_touch_pointer_id.set(pointer_id + 1);
2304 pointer_id
2305 }
2306
2307 fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2309 self.active_pointer_ids.borrow_mut().remove(&touch_id);
2310 }
2311
2312 fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2315 self.active_pointer_ids
2318 .borrow()
2319 .values()
2320 .min()
2321 .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2322 }
2323
2324 #[allow(clippy::too_many_arguments)]
2328 fn fire_pointer_event_for_touch(
2329 &self,
2330 cx: &mut js::context::JSContext,
2331 target_element: &Element,
2332 touch: &Touch,
2333 pointer_id: i32,
2334 event_name: &str,
2335 is_primary: bool,
2336 input_event: &ConstellationInputEvent,
2337 hit_test_result: &HitTestResult,
2338 ) {
2339 let mut targets: Vec<DomRoot<Node>> = vec![];
2341 let mut current: Option<DomRoot<Node>> = Some(DomRoot::from_ref(target_element.upcast()));
2342 while let Some(node) = current {
2343 targets.push(DomRoot::from_ref(&*node));
2344 current = node.parent_in_flat_tree();
2345 }
2346
2347 if event_name == "pointerenter" {
2349 targets.reverse();
2350 }
2351
2352 for target in targets {
2353 let pointer_event = touch.to_pointer_event(
2354 &self.window,
2355 event_name,
2356 pointer_id,
2357 is_primary,
2358 input_event.active_keyboard_modifiers,
2359 false,
2360 Some(hit_test_result.point_in_node),
2361 CanGc::from_cx(cx),
2362 );
2363 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2364 }
2365 }
2366
2367 pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2368 self.access_key_handlers
2369 .borrow()
2370 .values()
2371 .any(|value| &**value == element)
2372 }
2373
2374 pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2375 self.access_key_handlers
2376 .borrow_mut()
2377 .retain(|_, value| &**value != element)
2378 }
2379
2380 pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2381 let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2382 access_key_handlers
2384 .entry(code.into())
2385 .or_insert(Dom::from_ref(element));
2386 }
2387
2388 fn maybe_handle_accesskey(
2389 &self,
2390 cx: &mut js::context::JSContext,
2391 event: &KeyboardEvent,
2392 ) -> bool {
2393 #[cfg(target_os = "macos")]
2394 let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2395 #[cfg(not(target_os = "macos"))]
2396 let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2397
2398 if event.modifiers() != access_key_modifiers {
2399 return false;
2400 }
2401
2402 let Ok(code) = Code::from_str(&event.Code().str()) else {
2403 return false;
2404 };
2405
2406 let Some(html_element) = self
2407 .access_key_handlers
2408 .borrow()
2409 .get(&code.into())
2410 .map(|html_element| html_element.as_rooted())
2411 else {
2412 return false;
2413 };
2414
2415 let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2423 return false;
2424 };
2425
2426 if command.disabled() || command.hidden() {
2427 return false;
2428 }
2429
2430 let node = html_element.upcast::<Node>();
2431 if !node.is_connected() {
2432 return false;
2433 }
2434
2435 for node in node.inclusive_ancestors_unrooted(cx.no_gc(), ShadowIncluding::Yes) {
2436 if node
2437 .downcast::<HTMLElement>()
2438 .is_some_and(|html_element| html_element.Hidden())
2439 {
2440 return false;
2441 }
2442 }
2443
2444 self.focus_and_scroll_to_element_for_key_event(cx, html_element.upcast());
2447 command.perform_action(cx);
2448 true
2449 }
2450
2451 pub(crate) fn focus_and_scroll_to_element_for_key_event(
2452 &self,
2453 cx: &mut JSContext,
2454 element: &Element,
2455 ) {
2456 element.upcast::<Node>().run_the_focusing_steps(cx, None);
2457 let scroll_axis = ScrollAxisState {
2458 position: ScrollLogicalPosition::Center,
2459 requirement: ScrollRequirement::IfNotVisible,
2460 };
2461 element.scroll_into_view_with_options(
2462 cx,
2463 ScrollBehavior::Auto,
2464 scroll_axis,
2465 scroll_axis,
2466 None,
2467 None,
2468 );
2469 }
2470
2471 pub(crate) fn is_active_pointer(&self, pointer_id: i32) -> bool {
2474 if pointer_id == PointerId::Mouse as i32 {
2475 self.mouse_buttons_down.get() > 0
2477 } else {
2478 self.active_pointer_ids
2480 .borrow()
2481 .values()
2482 .any(|&id| id == pointer_id)
2483 }
2484 }
2485
2486 pub(crate) fn set_pending_pointer_capture(&self, pointer_id: i32, element: &Element) {
2489 self.pending_pointer_capture
2490 .borrow_mut()
2491 .insert(pointer_id, Dom::from_ref(element));
2492 }
2493
2494 pub(crate) fn clear_pending_pointer_capture(&self, pointer_id: i32) {
2497 self.pending_pointer_capture
2498 .borrow_mut()
2499 .remove(&pointer_id);
2500 }
2501
2502 pub(crate) fn get_pending_pointer_capture(&self, pointer_id: i32) -> Option<DomRoot<Element>> {
2504 self.pending_pointer_capture
2505 .borrow()
2506 .get(&pointer_id)
2507 .map(|el| DomRoot::from_ref(&**el))
2508 }
2509
2510 pub(crate) fn has_pointer_capture(&self, pointer_id: i32, element: &Element) -> bool {
2513 self.pending_pointer_capture
2514 .borrow()
2515 .get(&pointer_id)
2516 .is_some_and(|el| &**el == element)
2517 }
2518
2519 fn get_pointer_capture_target(&self, pointer_id: i32) -> Option<DomRoot<Element>> {
2524 self.pointer_capture_target
2525 .borrow()
2526 .get(&pointer_id)
2527 .map(|el| DomRoot::from_ref(&**el))
2528 .filter(|el| el.upcast::<Node>().is_connected())
2529 }
2530
2531 fn release_disconnected_pointer_capture(
2537 &self,
2538 cx: &mut JSContext,
2539 pointer_id: i32,
2540 pointer_type: &str,
2541 is_primary: bool,
2542 ) -> bool {
2543 let capture_target = self
2544 .pointer_capture_target
2545 .borrow()
2546 .get(&pointer_id)
2547 .map(|el| DomRoot::from_ref(&**el));
2548 if let Some(capture_element) = capture_target &&
2549 !capture_element.upcast::<Node>().is_connected()
2550 {
2551 let document = self.window.Document();
2553 self.fire_pointer_capture_event_at_target(
2554 cx,
2555 "lostpointercapture",
2556 pointer_id,
2557 pointer_type,
2558 is_primary,
2559 document.upcast::<EventTarget>(),
2560 );
2561 self.pending_pointer_capture
2563 .borrow_mut()
2564 .remove(&pointer_id);
2565 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2566 return true;
2567 }
2568 false
2569 }
2570
2571 fn fire_pointer_capture_event(
2574 &self,
2575 cx: &mut JSContext,
2576 event_type: &str,
2577 pointer_id: i32,
2578 pointer_type: &str,
2579 is_primary: bool,
2580 target: &Element,
2581 ) {
2582 self.fire_pointer_capture_event_at_target(
2583 cx,
2584 event_type,
2585 pointer_id,
2586 pointer_type,
2587 is_primary,
2588 target.upcast::<EventTarget>(),
2589 );
2590 }
2591
2592 fn fire_pointer_capture_event_at_target(
2594 &self,
2595 cx: &mut JSContext,
2596 event_type: &str,
2597 pointer_id: i32,
2598 pointer_type: &str,
2599 is_primary: bool,
2600 target: &EventTarget,
2601 ) {
2602 let pointer_event = PointerEvent::new(
2603 &self.window,
2604 event_type.into(),
2605 EventBubbles::Bubbles,
2606 EventCancelable::NotCancelable,
2607 Some(&self.window),
2608 0,
2609 Point2D::new(0, 0),
2610 Point2D::new(0, 0),
2611 Point2D::new(0, 0),
2612 Modifiers::empty(),
2613 0,
2614 0,
2615 None,
2616 None,
2617 pointer_id,
2618 1,
2619 1,
2620 0.0,
2621 0.0,
2622 0,
2623 0,
2624 0,
2625 PI / 2.0,
2626 0.0,
2627 DOMString::from(pointer_type),
2628 is_primary,
2629 vec![],
2630 vec![],
2631 CanGc::from_cx(cx),
2632 );
2633 pointer_event.upcast::<Event>().set_composed(true);
2634 pointer_event.upcast::<Event>().fire(cx, target);
2635 }
2636
2637 #[expect(clippy::too_many_arguments)]
2641 fn fire_pointer_boundary_event(
2642 &self,
2643 cx: &mut JSContext,
2644 event_type: &str,
2645 bubbles: EventBubbles,
2646 pointer_id: i32,
2647 pointer_type: &str,
2648 is_primary: bool,
2649 target: &Element,
2650 related_target: Option<&Element>,
2651 ) {
2652 let pointer_event = PointerEvent::new(
2653 &self.window,
2654 event_type.into(),
2655 bubbles,
2656 EventCancelable::NotCancelable,
2657 Some(&self.window),
2658 0,
2659 Point2D::new(0, 0),
2660 Point2D::new(0, 0),
2661 Point2D::new(0, 0),
2662 Modifiers::empty(),
2663 -1, self.mouse_buttons_down.get() as u16,
2665 related_target.map(|el| el.upcast::<EventTarget>()),
2666 None,
2667 pointer_id,
2668 1,
2669 1,
2670 0.0,
2671 0.0,
2672 0,
2673 0,
2674 0,
2675 PI / 2.0,
2676 0.0,
2677 DOMString::from(pointer_type),
2678 is_primary,
2679 vec![],
2680 vec![],
2681 CanGc::from_cx(cx),
2682 );
2683 pointer_event.upcast::<Event>().set_composed(true);
2684 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2685 }
2686
2687 fn fire_pointer_capture_boundary_transition(
2692 &self,
2693 cx: &mut JSContext,
2694 pointer_id: i32,
2695 pointer_type: &str,
2696 is_primary: bool,
2697 old_target: &Element,
2698 new_target: &Element,
2699 ) {
2700 if pointer_type != "mouse" {
2701 return;
2702 }
2703 if old_target == new_target {
2704 return;
2705 }
2706 self.fire_pointer_boundary_event(
2707 cx,
2708 "pointerout",
2709 EventBubbles::Bubbles,
2710 pointer_id,
2711 pointer_type,
2712 is_primary,
2713 old_target,
2714 Some(new_target),
2715 );
2716 self.fire_pointer_boundary_event(
2717 cx,
2718 "pointerleave",
2719 EventBubbles::DoesNotBubble,
2720 pointer_id,
2721 pointer_type,
2722 is_primary,
2723 old_target,
2724 Some(new_target),
2725 );
2726 self.fire_pointer_boundary_event(
2727 cx,
2728 "pointerover",
2729 EventBubbles::Bubbles,
2730 pointer_id,
2731 pointer_type,
2732 is_primary,
2733 new_target,
2734 Some(old_target),
2735 );
2736 self.fire_pointer_boundary_event(
2737 cx,
2738 "pointerenter",
2739 EventBubbles::DoesNotBubble,
2740 pointer_id,
2741 pointer_type,
2742 is_primary,
2743 new_target,
2744 Some(old_target),
2745 );
2746 }
2747
2748 fn implicit_release_pointer_capture(
2751 &self,
2752 cx: &mut JSContext,
2753 pointer_id: i32,
2754 pointer_type: &str,
2755 is_primary: bool,
2756 ) {
2757 let capture_element = self
2758 .pointer_capture_target
2759 .borrow()
2760 .get(&pointer_id)
2761 .map(|el| DomRoot::from_ref(&**el));
2762 if let Some(capture_element) = capture_element {
2763 if capture_element.upcast::<Node>().is_connected() {
2764 self.fire_pointer_capture_event(
2765 cx,
2766 "lostpointercapture",
2767 pointer_id,
2768 pointer_type,
2769 is_primary,
2770 &capture_element,
2771 );
2772 if let Some(hover_target) = self.current_hover_target.get() {
2775 self.fire_pointer_capture_boundary_transition(
2776 cx,
2777 pointer_id,
2778 pointer_type,
2779 is_primary,
2780 &capture_element,
2781 &hover_target,
2782 );
2783 }
2784 } else {
2785 let document = self.window.Document();
2786 self.fire_pointer_capture_event_at_target(
2787 cx,
2788 "lostpointercapture",
2789 pointer_id,
2790 pointer_type,
2791 is_primary,
2792 document.upcast::<EventTarget>(),
2793 );
2794 }
2795 }
2796 self.pending_pointer_capture
2797 .borrow_mut()
2798 .remove(&pointer_id);
2799 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2800 }
2801
2802 fn process_pending_pointer_capture(
2806 &self,
2807 cx: &mut JSContext,
2808 pointer_id: i32,
2809 pointer_type: &str,
2810 is_primary: bool,
2811 ) {
2812 let pending = self
2813 .pending_pointer_capture
2814 .borrow()
2815 .get(&pointer_id)
2816 .map(|el| DomRoot::from_ref(&**el));
2817 let current = self
2818 .pointer_capture_target
2819 .borrow()
2820 .get(&pointer_id)
2821 .map(|el| DomRoot::from_ref(&**el));
2822
2823 let pending_connected = pending
2826 .as_ref()
2827 .is_some_and(|el| el.upcast::<Node>().is_connected());
2828
2829 let pointer_is_active = self.is_active_pointer(pointer_id);
2832
2833 match (&pending, ¤t) {
2834 (Some(pending_el), None) if pending_connected => {
2835 if pointer_is_active {
2836 if let Some(hover_target) = self.current_hover_target.get() {
2839 self.fire_pointer_capture_boundary_transition(
2840 cx,
2841 pointer_id,
2842 pointer_type,
2843 is_primary,
2844 &hover_target,
2845 pending_el,
2846 );
2847 }
2848 self.fire_pointer_capture_event(
2849 cx,
2850 "gotpointercapture",
2851 pointer_id,
2852 pointer_type,
2853 is_primary,
2854 pending_el,
2855 );
2856 self.pointer_capture_target
2857 .borrow_mut()
2858 .insert(pointer_id, Dom::from_ref(pending_el));
2859 } else {
2860 self.pending_pointer_capture
2861 .borrow_mut()
2862 .remove(&pointer_id);
2863 }
2864 },
2865 (Some(pending_el), Some(current_el))
2866 if pending_connected && pending_el != current_el =>
2867 {
2868 self.fire_pointer_capture_event(
2869 cx,
2870 "lostpointercapture",
2871 pointer_id,
2872 pointer_type,
2873 is_primary,
2874 current_el,
2875 );
2876 if pointer_is_active {
2877 self.fire_pointer_capture_boundary_transition(
2878 cx,
2879 pointer_id,
2880 pointer_type,
2881 is_primary,
2882 current_el,
2883 pending_el,
2884 );
2885 self.fire_pointer_capture_event(
2886 cx,
2887 "gotpointercapture",
2888 pointer_id,
2889 pointer_type,
2890 is_primary,
2891 pending_el,
2892 );
2893 self.pointer_capture_target
2894 .borrow_mut()
2895 .insert(pointer_id, Dom::from_ref(pending_el));
2896 } else {
2897 self.pending_pointer_capture
2898 .borrow_mut()
2899 .remove(&pointer_id);
2900 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2901 }
2902 },
2903 (None, Some(current_el)) | (Some(_), Some(current_el)) if !pending_connected => {
2904 self.fire_pointer_capture_event(
2905 cx,
2906 "lostpointercapture",
2907 pointer_id,
2908 pointer_type,
2909 is_primary,
2910 current_el,
2911 );
2912 if let Some(hover_target) = self.current_hover_target.get() {
2915 self.fire_pointer_capture_boundary_transition(
2916 cx,
2917 pointer_id,
2918 pointer_type,
2919 is_primary,
2920 current_el,
2921 &hover_target,
2922 );
2923 }
2924 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2925 if !pending_connected {
2926 self.pending_pointer_capture
2927 .borrow_mut()
2928 .remove(&pointer_id);
2929 }
2930 },
2931 _ => {},
2932 }
2933 }
2934}
2935
2936pub(crate) fn character_to_code(character: char) -> Option<Code> {
2937 Some(match character.to_ascii_lowercase() {
2938 '`' => Code::Backquote,
2939 '\\' => Code::Backslash,
2940 '[' | '{' => Code::BracketLeft,
2941 ']' | '}' => Code::BracketRight,
2942 ',' | '<' => Code::Comma,
2943 '0' => Code::Digit0,
2944 '1' => Code::Digit1,
2945 '2' => Code::Digit2,
2946 '3' => Code::Digit3,
2947 '4' => Code::Digit4,
2948 '5' => Code::Digit5,
2949 '6' => Code::Digit6,
2950 '7' => Code::Digit7,
2951 '8' => Code::Digit8,
2952 '9' => Code::Digit9,
2953 '=' => Code::Equal,
2954 'a' => Code::KeyA,
2955 'b' => Code::KeyB,
2956 'c' => Code::KeyC,
2957 'd' => Code::KeyD,
2958 'e' => Code::KeyE,
2959 'f' => Code::KeyF,
2960 'g' => Code::KeyG,
2961 'h' => Code::KeyH,
2962 'i' => Code::KeyI,
2963 'j' => Code::KeyJ,
2964 'k' => Code::KeyK,
2965 'l' => Code::KeyL,
2966 'm' => Code::KeyM,
2967 'n' => Code::KeyN,
2968 'o' => Code::KeyO,
2969 'p' => Code::KeyP,
2970 'q' => Code::KeyQ,
2971 'r' => Code::KeyR,
2972 's' => Code::KeyS,
2973 't' => Code::KeyT,
2974 'u' => Code::KeyU,
2975 'v' => Code::KeyV,
2976 'w' => Code::KeyW,
2977 'x' => Code::KeyX,
2978 'y' => Code::KeyY,
2979 'z' => Code::KeyZ,
2980 '-' => Code::Minus,
2981 '.' => Code::Period,
2982 '\'' | '"' => Code::Quote,
2983 ';' => Code::Semicolon,
2984 '/' => Code::Slash,
2985 ' ' => Code::Space,
2986 _ => return None,
2987 })
2988}