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, NoGC};
25use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
26use layout_api::{HitTestFlags, ScrollContainerQueryFlags, node_id_from_scroll_id};
27use rustc_hash::FxHashMap;
28use script_bindings::cell::DomRefCell;
29use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
30use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
31use script_bindings::codegen::GenericBindings::EventBinding::EventMethods;
32use script_bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElementMethods;
33use script_bindings::codegen::GenericBindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
34use script_bindings::codegen::GenericBindings::KeyboardEventBinding::KeyboardEventMethods;
35use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
36use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
37use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
38use script_bindings::inheritance::Castable;
39use script_bindings::match_domstring_ascii;
40use script_bindings::num::Finite;
41use script_bindings::root::{Dom, DomRoot, DomSlice};
42use script_bindings::str::DOMString;
43use script_traits::{ConstellationInputEvent, MouseButtons};
44use servo_base::generic_channel::GenericCallback;
45use servo_config::pref;
46use servo_constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
47use style::Atom;
48use style_traits::CSSPixel;
49use webrender_api::ExternalScrollId;
50
51#[cfg(feature = "gamepad")]
52use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionName;
53use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
54use crate::dom::bindings::refcounted::Trusted;
55use crate::dom::bindings::root::MutNullableDom;
56use crate::dom::bindings::trace::NoTrace;
57use crate::dom::clipboardevent::ClipboardEventType;
58use crate::dom::document::FireMouseEventType;
59use crate::dom::document::focus::FocusableArea;
60use crate::dom::document::interactive_element_command::InteractiveElementCommand;
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::iterators::ShadowIncluding;
68use crate::dom::keyboardevent::KeyboardEvent;
69use crate::dom::node::focus::FocusTrigger;
70use crate::dom::node::{self, Node, NodeTraits};
71use crate::dom::pointerevent::{PointerEvent, PointerId};
72use crate::dom::types::{
73 ClipboardEvent, CompositionEvent, DataTransfer, Element, Event, EventTarget, GlobalScope,
74 HTMLAnchorElement, HTMLElement, HTMLLabelElement, MouseEvent, Touch, TouchEvent, TouchList,
75 WheelEvent, Window,
76};
77use crate::dom::virtualmethods::vtable_for;
78use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
79use crate::drag::drag_data_store::{DragDataStore, Kind, Mode};
80use crate::drag::drag_gesture::DragGesture;
81use crate::realms::enter_auto_realm;
82
83#[derive(Default, JSTraceable, MallocSizeOf)]
93struct ClickCountingInfo {
94 time: Option<Instant>,
95 #[no_trace]
96 point: Option<Point2D<f32, CSSPixel>>,
97 #[no_trace]
98 button: Option<MouseButton>,
99 count: usize,
100}
101
102impl ClickCountingInfo {
103 fn reset_click_count_if_necessary(
104 &mut self,
105 button: MouseButton,
106 point_in_frame: Point2D<f32, CSSPixel>,
107 ) {
108 let (Some(previous_button), Some(previous_point), Some(previous_time)) =
109 (self.button, self.point, self.time)
110 else {
111 assert_eq!(self.count, 0);
112 return;
113 };
114
115 let double_click_timeout =
116 Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
117 let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
118
119 let line = point_in_frame - previous_point;
121 let distance = (line.dot(line) as f64).sqrt();
122 if previous_button != button ||
123 Instant::now().duration_since(previous_time) > double_click_timeout ||
124 distance > double_click_distance_threshold as f64
125 {
126 self.count = 0;
127 self.time = None;
128 self.point = None;
129 }
130 }
131
132 fn increment_click_count(
133 &mut self,
134 button: MouseButton,
135 point: Point2D<f32, CSSPixel>,
136 ) -> usize {
137 self.time = Some(Instant::now());
138 self.point = Some(point);
139 self.button = Some(button);
140 self.count += 1;
141 self.count
142 }
143}
144
145#[derive(JSTraceable, MallocSizeOf)]
149#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
150pub(crate) struct DocumentEventHandler {
151 window: Dom<Window>,
153 #[no_trace]
155 #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
156 pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
157 mouse_move_event_index: DomRefCell<Option<usize>>,
159 #[no_trace]
161 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
162 coalesced_mouse_move_event_ids: DomRefCell<Vec<InputEventId>>,
163 wheel_event_index: DomRefCell<Option<usize>>,
168 #[no_trace]
170 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
171 coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
172 click_counting_info: DomRefCell<ClickCountingInfo>,
174 #[no_trace]
175 last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
176 #[no_trace]
180 mouse_button_state: Cell<MouseButtons>,
181 current_hover_target: MutNullableDom<Element>,
183 current_active_element: MutNullableDom<Element>,
186 most_recently_clicked_element: MutNullableDom<Element>,
188 #[no_trace]
190 most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
191 #[no_trace]
194 current_cursor: Cell<Option<Cursor>>,
195 active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
197 #[no_trace]
199 active_keyboard_modifiers: Cell<Modifiers>,
200 active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
202 next_touch_pointer_id: Cell<i32>,
204 access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
206 pending_pointer_capture: DomRefCell<FxHashMap<i32, Dom<Element>>>,
210 pointer_capture_target: DomRefCell<FxHashMap<i32, Dom<Element>>>,
213 drag_gesture: DomRefCell<Option<DragGesture>>,
216}
217
218impl DocumentEventHandler {
219 pub(crate) fn new(window: &Window) -> Self {
220 Self {
221 window: Dom::from_ref(window),
222 pending_input_events: Default::default(),
223 mouse_move_event_index: Default::default(),
224 coalesced_mouse_move_event_ids: Default::default(),
225 wheel_event_index: Default::default(),
226 coalesced_wheel_event_ids: Default::default(),
227 click_counting_info: Default::default(),
228 last_mouse_button_down_point: Default::default(),
229 mouse_button_state: Cell::new(MouseButtons::empty()),
230 current_hover_target: Default::default(),
231 current_active_element: Default::default(),
232 most_recently_clicked_element: Default::default(),
233 most_recent_mousemove_point: Default::default(),
234 current_cursor: Default::default(),
235 active_touch_points: Default::default(),
236 active_keyboard_modifiers: Default::default(),
237 active_pointer_ids: Default::default(),
238 next_touch_pointer_id: Cell::new(1),
239 access_key_handlers: Default::default(),
240 pending_pointer_capture: Default::default(),
241 pointer_capture_target: Default::default(),
242 drag_gesture: Default::default(),
243 }
244 }
245
246 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
248 let mut pending_input_events = self.pending_input_events.borrow_mut();
249 if matches!(event.event.event, InputEvent::MouseMove(..)) {
250 if let Some(mouse_move_event) = self
252 .mouse_move_event_index
253 .borrow()
254 .and_then(|index| pending_input_events.get_mut(index))
255 {
256 self.coalesced_mouse_move_event_ids
257 .borrow_mut()
258 .push(mouse_move_event.event.id);
259 *mouse_move_event = event;
260 return;
261 }
262
263 *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
264 }
265
266 if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
267 if let Some(existing_constellation_wheel_event) = self
269 .wheel_event_index
270 .borrow()
271 .and_then(|index| pending_input_events.get_mut(index)) &&
272 let InputEvent::Wheel(ref mut existing_wheel_event) =
273 existing_constellation_wheel_event.event.event &&
274 existing_wheel_event.delta.mode == new_wheel_event.delta.mode
275 {
276 self.coalesced_wheel_event_ids
277 .borrow_mut()
278 .push(existing_constellation_wheel_event.event.id);
279 existing_wheel_event.delta.x += new_wheel_event.delta.x;
280 existing_wheel_event.delta.y += new_wheel_event.delta.y;
281 existing_wheel_event.delta.z += new_wheel_event.delta.z;
282 existing_wheel_event.point = new_wheel_event.point;
283 existing_constellation_wheel_event.event.id = event.event.id;
284 return;
285 }
286
287 *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
288 }
289
290 pending_input_events.push(event);
291 }
292
293 pub(crate) fn has_pending_input_events(&self) -> bool {
296 !self.pending_input_events.borrow().is_empty()
297 }
298
299 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
300 #[cfg(target_os = "macos")]
301 {
302 self.active_keyboard_modifiers
303 .get()
304 .contains(Modifiers::META)
305 }
306 #[cfg(not(target_os = "macos"))]
307 {
308 self.active_keyboard_modifiers
309 .get()
310 .contains(Modifiers::CONTROL)
311 }
312 }
313
314 pub(crate) fn handle_pending_input_events(&self, cx: &mut JSContext) {
315 debug_assert!(
316 !self.pending_input_events.borrow().is_empty(),
317 "handle_pending_input_events called with no events"
318 );
319 let mut realm = enter_auto_realm(cx, &*self.window);
320 let cx = &mut realm.current_realm();
321
322 *self.mouse_move_event_index.safe_borrow_mut(cx.no_gc()) = None;
324 *self.wheel_event_index.safe_borrow_mut(cx.no_gc()) = None;
325 let pending_input_events =
326 mem::take(&mut *self.pending_input_events.safe_borrow_mut(cx.no_gc()));
327 let mut coalesced_mouse_move_event_ids = mem::take(
328 &mut *self
329 .coalesced_mouse_move_event_ids
330 .safe_borrow_mut(cx.no_gc()),
331 );
332 let mut coalesced_wheel_event_ids =
333 mem::take(&mut *self.coalesced_wheel_event_ids.safe_borrow_mut(cx.no_gc()));
334
335 let mut input_event_outcomes = Vec::with_capacity(
336 pending_input_events.len() +
337 coalesced_mouse_move_event_ids.len() +
338 coalesced_wheel_event_ids.len(),
339 );
340 for event in pending_input_events {
346 self.active_keyboard_modifiers
347 .set(event.active_keyboard_modifiers);
348 let result = match event.event.event {
349 InputEvent::MouseButton(mouse_button_event) => {
350 self.handle_native_mouse_button_event(cx, mouse_button_event, &event);
351 InputEventResult::default()
352 },
353 InputEvent::MouseMove(_) => {
354 self.handle_native_mouse_move_event(cx, &event);
355 input_event_outcomes.extend(
356 mem::take(&mut coalesced_mouse_move_event_ids)
357 .into_iter()
358 .map(|id| InputEventOutcome {
359 id,
360 result: InputEventResult::default(),
361 }),
362 );
363 InputEventResult::default()
364 },
365 InputEvent::MouseLeftViewport(mouse_leave_event) => {
366 self.handle_mouse_left_viewport_event(cx, &event, &mouse_leave_event);
367 InputEventResult::default()
368 },
369 InputEvent::Touch(touch_event) => self.handle_touch_event(cx, touch_event, &event),
370 InputEvent::Wheel(wheel_event) => {
371 let result = self.handle_wheel_event(cx, wheel_event, &event);
372 input_event_outcomes.extend(
373 mem::take(&mut coalesced_wheel_event_ids)
374 .into_iter()
375 .map(|id| InputEventOutcome { id, result }),
376 );
377 result
378 },
379 InputEvent::Keyboard(keyboard_event) => {
380 self.handle_keyboard_event(cx, keyboard_event)
381 },
382 InputEvent::Ime(ime_event) => self.handle_ime_event(cx, ime_event),
383 #[cfg(feature = "gamepad")]
384 InputEvent::Gamepad(gamepad_event) => {
385 self.handle_gamepad_event(gamepad_event);
386 InputEventResult::default()
387 },
388 InputEvent::EditingAction(editing_action_event) => {
389 self.handle_editing_action(cx, None, editing_action_event)
390 },
391 };
392
393 input_event_outcomes.push(InputEventOutcome {
394 id: event.event.id,
395 result,
396 });
397 }
398
399 self.notify_embedder_that_events_were_handled(input_event_outcomes);
400 }
401
402 fn notify_embedder_that_events_were_handled(
403 &self,
404 input_event_outcomes: Vec<InputEventOutcome>,
405 ) {
406 let trusted_window = Trusted::new(&*self.window);
409 self.window
410 .as_global_scope()
411 .task_manager()
412 .dom_manipulation_task_source()
413 .queue(task!(notify_webdriver_input_event_completed: move || {
414 let window = trusted_window.root();
415 window.send_to_embedder(
416 EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
417 }));
418 }
419
420 fn target_for_events_following_focus(&self) -> DomRoot<EventTarget> {
424 let document = self.window.Document();
425 match &*document.focus_handler().focused_area() {
426 FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
427 FocusableArea::IFrameViewport { iframe_element, .. } => {
428 DomRoot::from_ref(iframe_element.upcast())
429 },
430 FocusableArea::Viewport => document
431 .GetBody()
432 .map(DomRoot::upcast)
433 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast())),
434 }
435 }
436
437 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
438 if cursor == self.current_cursor.get() {
439 return;
440 }
441 self.current_cursor.set(cursor);
442 self.window.send_to_embedder(EmbedderMsg::SetCursor(
443 self.window.webview_id(),
444 cursor.unwrap_or_default(),
445 ));
446 }
447
448 fn handle_mouse_left_viewport_event(
449 &self,
450 cx: &mut JSContext,
451 input_event: &ConstellationInputEvent,
452 mouse_leave_event: &MouseLeftViewportEvent,
453 ) {
454 if let Some(current_hover_target) = self.current_hover_target.get() {
455 let current_hover_target = current_hover_target.upcast::<Node>();
456 for element in current_hover_target
457 .inclusive_ancestors(ShadowIncluding::Yes)
458 .filter_map(DomRoot::downcast::<Element>)
459 {
460 element.set_hover_state(false);
461 }
462
463 if let Some(hit_test_result) =
464 self.most_recent_mousemove_point.get().and_then(|point| {
465 self.window
466 .hit_test_from_point_in_viewport(HitTestFlags::empty(), point)
467 })
468 {
469 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
470 cx,
471 &self.window,
472 FireMouseEventType::Out,
473 &hit_test_result,
474 input_event,
475 );
476
477 mouse_out_event
479 .to_pointer_hover_event(cx, "pointerout")
480 .upcast::<Event>()
481 .fire(cx, current_hover_target.upcast());
482
483 mouse_out_event
484 .upcast::<Event>()
485 .fire(cx, current_hover_target.upcast());
486
487 self.handle_mouse_enter_leave_event(
488 cx,
489 current_hover_target,
490 None,
491 FireMouseEventType::Leave,
492 &hit_test_result,
493 input_event,
494 );
495 }
496 }
497
498 if !mouse_leave_event.focus_moving_to_another_iframe {
503 self.window
507 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
508 self.set_cursor(None);
509 } else {
510 self.current_cursor.set(None);
511 }
512
513 self.current_hover_target.set(None);
514 self.most_recent_mousemove_point.set(None);
515 }
516
517 fn handle_mouse_enter_leave_event(
518 &self,
519 cx: &mut JSContext,
520 event_target: &Node,
521 related_target: Option<&Node>,
522 event_type: FireMouseEventType,
523 hit_test_result: &HitTestResult,
524 input_event: &ConstellationInputEvent,
525 ) {
526 assert!(matches!(
527 event_type,
528 FireMouseEventType::Enter | FireMouseEventType::Leave
529 ));
530
531 let common_ancestor = match related_target.as_ref() {
532 Some(related_target) => event_target
533 .common_ancestor_in_flat_tree(cx.no_gc(), related_target)
534 .unwrap_or_else(|| DomRoot::from_ref(event_target)),
535 None => DomRoot::from_ref(event_target),
536 };
537
538 let mut targets: Vec<_> = event_target
541 .inclusive_ancestors_in_flat_tree_unrooted(cx.no_gc())
542 .take_while(|node| *node != *common_ancestor)
543 .map(|node| node.as_rooted())
544 .collect();
545
546 if event_type == FireMouseEventType::Enter {
549 targets.reverse();
550 }
551
552 let pointer_event_name = match event_type {
553 FireMouseEventType::Enter => "pointerenter",
554 FireMouseEventType::Leave => "pointerleave",
555 _ => unreachable!(),
556 };
557
558 for target in targets {
559 let mouse_event = MouseEvent::new_for_platform_motion_event(
560 cx,
561 &self.window,
562 event_type,
563 hit_test_result,
564 input_event,
565 );
566 mouse_event
567 .upcast::<Event>()
568 .set_related_target(related_target.as_ref().map(|target| target.upcast()));
569
570 mouse_event
572 .to_pointer_hover_event(cx, pointer_event_name)
573 .upcast::<Event>()
574 .fire(cx, target.upcast());
575
576 mouse_event.upcast::<Event>().fire(cx, target.upcast());
578 }
579 }
580
581 fn handle_native_mouse_move_event(
583 &self,
584 cx: &mut JSContext,
585 input_event: &ConstellationInputEvent,
586 ) {
587 let pointer_id = PointerId::Mouse as i32;
590 let released_disconnected =
591 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
592
593 let hit_test_flags = if self
594 .drag_gesture
595 .borrow()
596 .as_ref()
597 .is_some_and(DragGesture::need_dom_position_from_hit_test)
598 {
599 HitTestFlags::IncludeDomPosition
600 } else {
601 HitTestFlags::empty()
602 };
603
604 let Some(hit_test_result) = self
609 .window
610 .hit_test_from_input_event(hit_test_flags, input_event)
611 else {
612 return;
613 };
614
615 {
616 let mut maybe_drag_gesture = self.drag_gesture.borrow_mut();
617 if maybe_drag_gesture.as_mut().is_some_and(|drag_gesture| {
618 !drag_gesture.handle_mouse_move_event(cx, input_event, &hit_test_result)
619 }) {
620 *maybe_drag_gesture = None;
621 }
622 }
623
624 let old_mouse_move_point = self
625 .most_recent_mousemove_point
626 .replace(Some(hit_test_result.point_in_frame));
627 if old_mouse_move_point == Some(hit_test_result.point_in_frame) {
628 return;
629 }
630
631 self.set_cursor(Some(hit_test_result.cursor));
633
634 let Some(new_target) = hit_test_result
635 .node
636 .inclusive_ancestors(ShadowIncluding::Yes)
637 .find_map(DomRoot::downcast::<Element>)
638 else {
639 return;
640 };
641
642 let capture_is_active = self.get_pointer_capture_target(pointer_id).is_some();
643 let old_hover_target = self.current_hover_target.get();
644 let target_has_changed = old_hover_target
645 .as_ref()
646 .is_none_or(|old_target| *old_target != new_target);
647
648 if target_has_changed {
651 if let Some(old_target) = self.current_hover_target.get() {
653 let old_target_is_ancestor_of_new_target = old_target
654 .upcast::<Node>()
655 .is_ancestor_of(new_target.upcast::<Node>());
656
657 if !old_target_is_ancestor_of_new_target {
660 for element in old_target
661 .upcast::<Node>()
662 .inclusive_ancestors(ShadowIncluding::Yes)
663 .filter_map(DomRoot::downcast::<Element>)
664 {
665 element.set_hover_state(false);
666 }
667 }
668
669 if !capture_is_active {
670 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
671 cx,
672 &self.window,
673 FireMouseEventType::Out,
674 &hit_test_result,
675 input_event,
676 );
677 mouse_out_event
678 .upcast::<Event>()
679 .set_related_target(Some(new_target.upcast()));
680
681 mouse_out_event
683 .to_pointer_hover_event(cx, "pointerout")
684 .upcast::<Event>()
685 .fire(cx, old_target.upcast());
686
687 mouse_out_event
688 .upcast::<Event>()
689 .fire(cx, old_target.upcast());
690
691 if !old_target_is_ancestor_of_new_target {
692 let event_target = old_target.upcast::<Node>();
693 let moving_into = Some(new_target.upcast::<Node>());
694 self.handle_mouse_enter_leave_event(
695 cx,
696 event_target,
697 moving_into,
698 FireMouseEventType::Leave,
699 &hit_test_result,
700 input_event,
701 );
702 }
703 }
704 }
705
706 for element in new_target
708 .upcast::<Node>()
709 .inclusive_ancestors(ShadowIncluding::Yes)
710 .filter_map(DomRoot::downcast::<Element>)
711 {
712 element.set_hover_state(true);
713 }
714
715 if !capture_is_active {
716 let mouse_over_event = MouseEvent::new_for_platform_motion_event(
717 cx,
718 &self.window,
719 FireMouseEventType::Over,
720 &hit_test_result,
721 input_event,
722 );
723 mouse_over_event
724 .upcast::<Event>()
725 .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
726
727 mouse_over_event
729 .to_pointer_hover_event(cx, "pointerover")
730 .upcast::<Event>()
731 .dispatch(cx, new_target.upcast(), false);
732
733 mouse_over_event
734 .upcast::<Event>()
735 .dispatch(cx, new_target.upcast(), false);
736
737 let moving_from = old_hover_target
738 .as_ref()
739 .map(|old_target| old_target.upcast::<Node>());
740 let event_target = new_target.upcast::<Node>();
741 self.handle_mouse_enter_leave_event(
742 cx,
743 event_target,
744 moving_from,
745 FireMouseEventType::Enter,
746 &hit_test_result,
747 input_event,
748 );
749 }
750 }
751
752 let mouse_event = MouseEvent::new_for_platform_motion_event(
755 cx,
756 &self.window,
757 FireMouseEventType::Move,
758 &hit_test_result,
759 input_event,
760 );
761
762 let pointer_target = self
767 .get_pointer_capture_target(pointer_id)
768 .map(DomRoot::upcast::<EventTarget>)
769 .unwrap_or_else(|| DomRoot::from_ref(new_target.upcast::<EventTarget>()));
770
771 let pointer_event = mouse_event.to_pointer_event(cx, Atom::from("pointermove"));
772 pointer_event.upcast::<Event>().set_composed(true);
773 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
774
775 if !released_disconnected {
779 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
780 }
781
782 mouse_event.upcast::<Event>().fire(cx, &pointer_target);
784
785 self.update_current_hover_target_and_status(cx.no_gc(), Some(new_target));
786 }
787
788 fn update_current_hover_target_and_status(
789 &self,
790 no_gc: &NoGC,
791 new_hover_target: Option<DomRoot<Element>>,
792 ) {
793 let current_hover_target = self.current_hover_target.get();
794 if current_hover_target == new_hover_target {
795 return;
796 }
797
798 let previous_hover_target = self.current_hover_target.get();
799 self.current_hover_target.set(new_hover_target.as_deref());
800
801 if let Some(target) = self.current_hover_target.get() &&
804 let Some(anchor) = target
805 .upcast::<Node>()
806 .inclusive_ancestors(ShadowIncluding::Yes)
807 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
808 {
809 let status = anchor
810 .full_href_url_for_user_interface(no_gc)
811 .map(|url| url.to_string());
812 self.window
813 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
814 return;
815 }
816
817 if previous_hover_target.is_none_or(|previous_hover_target| {
822 previous_hover_target
823 .upcast::<Node>()
824 .inclusive_ancestors(ShadowIncluding::Yes)
825 .any(|node| node.is::<HTMLAnchorElement>())
826 }) {
827 self.window
828 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
829 }
830 }
831
832 pub(crate) fn handle_refresh_cursor(&self) {
833 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
834 return;
835 };
836
837 let Some(hit_test_result) = self
838 .window
839 .hit_test_from_point_in_viewport(HitTestFlags::empty(), most_recent_mousemove_point)
840 else {
841 return;
842 };
843
844 self.set_cursor(Some(hit_test_result.cursor));
845 }
846
847 fn set_active_element(&self, original_target: &Element) {
848 let find_element_for_activation = |element: &Element| {
849 let node: &Node = element.upcast();
850 if node.is_in_ua_widget() &&
851 let Some(containing_shadow_root) = node.containing_shadow_root()
852 {
853 return containing_shadow_root.Host();
854 }
855
856 if node.type_id() ==
858 NodeTypeId::Element(ElementTypeId::HTMLElement(
859 HTMLElementTypeId::HTMLLabelElement,
860 ))
861 {
862 let label = element.downcast::<HTMLLabelElement>().unwrap();
863 if let Some(control) = label.GetControl() {
864 return DomRoot::from_ref(control.upcast::<Element>());
865 }
866 }
867
868 DomRoot::from_ref(element)
869 };
870 let element_for_activation = find_element_for_activation(original_target);
871
872 if let Some(currently_active_element) = self.current_active_element.get() {
875 if currently_active_element == element_for_activation {
876 return;
877 }
878 self.unset_active_element();
879 }
880
881 element_for_activation.set_active_state(true);
882 self.current_active_element
883 .set(Some(&*element_for_activation));
884 }
885
886 fn unset_active_element(&self) {
887 if let Some(active_element) = self.current_active_element.take() {
888 active_element.set_active_state(false);
889 }
890 }
891
892 fn handle_native_mouse_button_event(
895 &self,
896 cx: &mut JSContext,
897 mouse_button_event: MouseButtonEvent,
898 input_event: &ConstellationInputEvent,
899 ) {
900 {
901 let mut maybe_drag_gesture = self.drag_gesture.borrow_mut();
902 if maybe_drag_gesture
903 .as_mut()
904 .is_some_and(|drag_gesture| !drag_gesture.handle_mouse_button_event(input_event))
905 {
906 *maybe_drag_gesture = None;
907 }
908 }
909
910 let flags = if input_event.primary_button_is_pressed() ||
911 input_event.auxiliary_button_is_pressed()
912 {
913 HitTestFlags::IncludeDomPosition
914 } else {
915 HitTestFlags::empty()
916 };
917 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
919 else {
920 return;
921 };
922
923 debug!(
924 "{:?}: at {:?}",
925 mouse_button_event.action, hit_test_result.point_in_frame
926 );
927
928 let document = self.window.Document();
931 if mouse_button_event.action == MouseButtonAction::Down {
932 document
933 .focus_handler()
934 .set_sequential_focus_navigation_starting_point(&hit_test_result.node);
935 }
936
937 let Some(element) = hit_test_result
938 .node
939 .inclusive_ancestors(ShadowIncluding::Yes)
940 .find_map(DomRoot::downcast::<Element>)
941 else {
942 return;
943 };
944
945 let node = element.upcast::<Node>();
946 debug!("{:?} on {:?}", mouse_button_event.action, node.debug_str());
947
948 if mouse_button_event.button == MouseButton::Primary {
953 if mouse_button_event.action == MouseButtonAction::Down {
954 self.set_active_element(&element);
955 }
956 if mouse_button_event.action == MouseButtonAction::Up {
957 self.unset_active_element();
958 }
959 }
960
961 if element.is_actually_disabled() {
965 return;
966 }
967
968 let mouse_event_type = match mouse_button_event.action {
969 embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
970 embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
971 };
972
973 if mouse_button_event.action == MouseButtonAction::Down {
980 self.click_counting_info
981 .safe_borrow_mut(cx.no_gc())
982 .reset_click_count_if_necessary(
983 mouse_button_event.button,
984 hit_test_result.point_in_frame,
985 );
986 }
987
988 let mouse_event = MouseEvent::for_platform_button_event(
989 cx,
990 mouse_event_type,
991 mouse_button_event,
992 input_event.pressed_mouse_buttons,
993 &self.window,
994 &hit_test_result,
995 input_event.active_keyboard_modifiers,
996 self.click_counting_info.borrow().count + 1,
997 );
998
999 match mouse_button_event.action {
1000 MouseButtonAction::Down => {
1001 self.last_mouse_button_down_point
1002 .set(Some(hit_test_result.point_in_frame));
1003
1004 let pointer_event_name = if self.mouse_button_state.get().is_empty() {
1006 "pointerdown".into()
1011 } else {
1012 "pointermove".into()
1018 };
1019 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1020
1021 let pointer_id = PointerId::Mouse as i32;
1023
1024 let released_disconnected =
1026 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1027
1028 let pointer_target = self
1030 .get_pointer_capture_target(pointer_id)
1031 .map(DomRoot::upcast::<EventTarget>)
1032 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1033
1034 self.mouse_button_state
1036 .set(input_event.pressed_mouse_buttons);
1037
1038 let pointer_event_result =
1039 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1040
1041 if !released_disconnected {
1045 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1046 }
1047
1048 let result = mouse_event
1050 .upcast::<Event>()
1051 .dispatch(cx, node.upcast(), false);
1052
1053 if result &&
1057 pointer_event_result &&
1058 let Some(node) = mouse_event
1059 .upcast::<Event>()
1060 .GetTarget()
1061 .and_then(DomRoot::downcast::<Node>)
1062 {
1063 vtable_for(&node).handle_mousedown_event(cx, &mouse_event, &hit_test_result);
1064 }
1065
1066 if result {
1069 document
1073 .focus_handler()
1074 .focus(cx, &node.find_click_focusable_area(cx));
1075 }
1076
1077 if let MouseButton::Secondary = mouse_button_event.button {
1080 self.maybe_show_context_menu(cx, node.upcast(), &hit_test_result, input_event);
1081 }
1082 },
1083 MouseButtonAction::Up => {
1085 let mouse_button_state = self.mouse_button_state.get();
1087 let exactly_one_button = mouse_button_state.exactly_one_button_pressed();
1088 let pointer_event_name = if exactly_one_button {
1089 "pointerup".into()
1094 } else {
1095 "pointermove".into()
1101 };
1102 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1103
1104 let pointer_id = PointerId::Mouse as i32;
1106
1107 let released_disconnected =
1109 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1110
1111 let pointer_target = self
1113 .get_pointer_capture_target(pointer_id)
1114 .map(DomRoot::upcast::<EventTarget>)
1115 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1116
1117 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1118
1119 self.mouse_button_state
1122 .set(input_event.pressed_mouse_buttons);
1123
1124 if !released_disconnected {
1128 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1129 }
1130
1131 if exactly_one_button {
1133 self.implicit_release_pointer_capture(cx, pointer_id, "mouse", true);
1134 }
1135
1136 mouse_event
1138 .upcast::<Event>()
1139 .dispatch(cx, node.upcast(), false);
1140
1141 self.click_counting_info
1145 .safe_borrow_mut(cx.no_gc())
1146 .increment_click_count(
1147 mouse_button_event.button,
1148 hit_test_result.point_in_frame,
1149 );
1150
1151 self.maybe_trigger_click_for_mouse_button_down_event(
1152 cx,
1153 mouse_button_event,
1154 input_event,
1155 &hit_test_result,
1156 &element,
1157 );
1158 },
1159 }
1160 }
1161
1162 fn maybe_trigger_click_for_mouse_button_down_event(
1165 &self,
1166 cx: &mut JSContext,
1167 event: MouseButtonEvent,
1168 input_event: &ConstellationInputEvent,
1169 hit_test_result: &HitTestResult,
1170 element: &Element,
1171 ) {
1172 if event.button != MouseButton::Primary {
1173 return;
1174 }
1175
1176 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
1177 return;
1178 };
1179
1180 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
1181 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
1182 if distance > maximum_click_distance {
1183 return;
1184 }
1185
1186 let element = &element.inclusive_ancestor_element_in_non_ua_shadow_root();
1190 self.most_recently_clicked_element.set(Some(element));
1191
1192 let click_count = self.click_counting_info.borrow().count;
1193 element.set_click_in_progress(true);
1194 MouseEvent::for_platform_button_event(
1195 cx,
1196 atom!("click"),
1197 event,
1198 input_event.pressed_mouse_buttons,
1199 &self.window,
1200 hit_test_result,
1201 input_event.active_keyboard_modifiers,
1202 click_count,
1203 )
1204 .upcast::<Event>()
1205 .dispatch(cx, element.upcast(), false);
1206 element.set_click_in_progress(false);
1207
1208 if click_count.is_multiple_of(2) {
1217 MouseEvent::for_platform_button_event(
1218 cx,
1219 Atom::from("dblclick"),
1220 event,
1221 input_event.pressed_mouse_buttons,
1222 &self.window,
1223 hit_test_result,
1224 input_event.active_keyboard_modifiers,
1225 2,
1226 )
1227 .upcast::<Event>()
1228 .dispatch(cx, element.upcast(), false);
1229 }
1230 }
1231
1232 fn maybe_show_context_menu(
1234 &self,
1235 cx: &mut js::context::JSContext,
1236 target: &EventTarget,
1237 hit_test_result: &HitTestResult,
1238 input_event: &ConstellationInputEvent,
1239 ) {
1240 let menu_event = PointerEvent::new(
1242 cx,
1243 &self.window, "contextmenu".into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
1250 hit_test_result.point_in_frame.to_i32(),
1251 hit_test_result
1252 .point_relative_to_initial_containing_block
1253 .to_i32(),
1254 input_event.active_keyboard_modifiers,
1255 MouseButton::Secondary,
1256 input_event.pressed_mouse_buttons,
1257 None, None, PointerId::Mouse as i32, 1, 1, 0.5, 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from_static("mouse"), true, vec![], vec![], );
1274 menu_event.upcast::<Event>().set_composed(true);
1275
1276 let result = menu_event.upcast::<Event>().fire(cx, target);
1278
1279 if result {
1281 self.window
1282 .Document()
1283 .embedder_controls()
1284 .show_context_menu(cx.no_gc(), hit_test_result);
1285 };
1286 }
1287
1288 fn handle_touch_event(
1289 &self,
1290 cx: &mut JSContext,
1291 event: EmbedderTouchEvent,
1292 input_event: &ConstellationInputEvent,
1293 ) -> InputEventResult {
1294 let flags = HitTestFlags::empty();
1295 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1297 else {
1298 self.update_active_touch_points_when_early_return(event);
1299 return Default::default();
1300 };
1301
1302 let TouchId(identifier) = event.touch_id;
1303
1304 let Some(element) = hit_test_result
1305 .node
1306 .inclusive_ancestors(ShadowIncluding::Yes)
1307 .find_map(DomRoot::downcast::<Element>)
1308 else {
1309 self.update_active_touch_points_when_early_return(event);
1310 return Default::default();
1311 };
1312
1313 let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1314 let window = &*self.window;
1315
1316 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1317 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1318 let page_x =
1319 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1320 let page_y =
1321 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1322
1323 let pointer_touch = Touch::new(
1325 cx,
1326 window,
1327 identifier,
1328 ¤t_target,
1329 client_x,
1330 client_y, client_x,
1332 client_y,
1333 page_x,
1334 page_y,
1335 );
1336
1337 let pointer_event_name = match event.event_type {
1339 TouchEventType::Down => "pointerdown",
1340 TouchEventType::Move => "pointermove",
1341 TouchEventType::Up => "pointerup",
1342 TouchEventType::Cancel => "pointercancel",
1343 };
1344
1345 let pointer_type = match event.pointer_type {
1347 TouchPointerType::Pen => "pen",
1348 TouchPointerType::Touch => "touch",
1349 };
1350
1351 let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1353 let is_primary = self.is_primary_pointer(pointer_id);
1354
1355 if matches!(event.event_type, TouchEventType::Down) {
1358 let pointer_over = pointer_touch.to_pointer_event(
1360 cx,
1361 window,
1362 "pointerover",
1363 pointer_id,
1364 is_primary,
1365 pointer_type,
1366 input_event.active_keyboard_modifiers,
1367 true, Some(hit_test_result.point_in_node),
1369 );
1370 pointer_over.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 "pointerenter",
1379 is_primary,
1380 pointer_type,
1381 input_event,
1382 &hit_test_result,
1383 );
1384 }
1385
1386 let released_disconnected = if matches!(event.event_type, TouchEventType::Cancel) {
1390 false
1391 } else {
1392 self.release_disconnected_pointer_capture(cx, pointer_id, pointer_type, is_primary)
1393 };
1394
1395 let pointer_target = self
1397 .get_pointer_capture_target(pointer_id)
1398 .map(DomRoot::upcast::<EventTarget>)
1399 .unwrap_or_else(|| current_target.clone());
1400
1401 let pointer_event = pointer_touch.to_pointer_event(
1402 cx,
1403 window,
1404 pointer_event_name,
1405 pointer_id,
1406 is_primary,
1407 pointer_type,
1408 input_event.active_keyboard_modifiers,
1409 event.is_cancelable(),
1410 Some(hit_test_result.point_in_node),
1411 );
1412 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1413
1414 if !released_disconnected && !matches!(event.event_type, TouchEventType::Cancel) {
1420 self.process_pending_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1421 }
1422
1423 if matches!(
1426 event.event_type,
1427 TouchEventType::Up | TouchEventType::Cancel
1428 ) {
1429 self.implicit_release_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1430 }
1431
1432 if matches!(
1435 event.event_type,
1436 TouchEventType::Up | TouchEventType::Cancel
1437 ) {
1438 let pointer_out = pointer_touch.to_pointer_event(
1440 cx,
1441 window,
1442 "pointerout",
1443 pointer_id,
1444 is_primary,
1445 pointer_type,
1446 input_event.active_keyboard_modifiers,
1447 true, Some(hit_test_result.point_in_node),
1449 );
1450 pointer_out.upcast::<Event>().fire(cx, ¤t_target);
1451
1452 self.fire_pointer_event_for_touch(
1454 cx,
1455 &element,
1456 &pointer_touch,
1457 pointer_id,
1458 "pointerleave",
1459 is_primary,
1460 pointer_type,
1461 input_event,
1462 &hit_test_result,
1463 );
1464 }
1465
1466 let (touch_dispatch_target, changed_touch) = match event.event_type {
1467 TouchEventType::Down => {
1468 self.active_touch_points
1470 .safe_borrow_mut(cx.no_gc())
1471 .push(Dom::from_ref(&*pointer_touch));
1472 self.set_active_element(&element);
1473 (current_target, pointer_touch)
1474 },
1475 _ => {
1476 let active_touch_points = self.active_touch_points.borrow();
1482 let Some(index) = active_touch_points
1483 .iter()
1484 .position(|point| point.Identifier() == identifier)
1485 else {
1486 warn!("No active touch point for {:?}", event.event_type);
1487 return Default::default();
1488 };
1489 let original_target = active_touch_points[index].Target();
1491 drop(active_touch_points);
1492
1493 let touch_with_touchstart_target = Touch::new(
1494 cx,
1495 window,
1496 identifier,
1497 &original_target,
1498 client_x,
1499 client_y,
1500 client_x,
1501 client_y,
1502 page_x,
1503 page_y,
1504 );
1505
1506 let mut active_touch_points = self.active_touch_points.safe_borrow_mut(cx.no_gc());
1507 match event.event_type {
1509 TouchEventType::Move => {
1510 active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1511 },
1512 TouchEventType::Up | TouchEventType::Cancel => {
1513 active_touch_points.swap_remove(index);
1514 self.remove_pointer_id_for_touch(identifier);
1515 self.unset_active_element();
1516 },
1517 TouchEventType::Down => unreachable!("Should have been handled above"),
1518 }
1519 (original_target, touch_with_touchstart_target)
1520 },
1521 };
1522
1523 rooted_vec!(let mut target_touches);
1524 target_touches.extend(
1525 self.active_touch_points
1526 .borrow()
1527 .iter()
1528 .filter(|touch| touch.Target() == touch_dispatch_target)
1529 .cloned(),
1530 );
1531
1532 let event_name = match event.event_type {
1533 TouchEventType::Down => "touchstart",
1534 TouchEventType::Move => "touchmove",
1535 TouchEventType::Up => "touchend",
1536 TouchEventType::Cancel => "touchcancel",
1537 };
1538
1539 let touches = TouchList::new(cx, window, self.active_touch_points.borrow().r());
1540 let changed_touches = TouchList::new(cx, window, from_ref(&&*changed_touch));
1541 let target_touches = TouchList::new(cx, window, target_touches.r());
1542
1543 let touch_event = TouchEvent::new(
1544 cx,
1545 window,
1546 event_name.into(),
1547 EventBubbles::Bubbles,
1548 EventCancelable::from(event.is_cancelable()),
1549 EventComposed::Composed,
1550 Some(window),
1551 0i32,
1552 &touches,
1553 &changed_touches,
1554 &target_touches,
1555 false,
1557 false,
1558 false,
1559 false,
1560 );
1561 let event = touch_event.upcast::<Event>();
1562 event.fire(cx, &touch_dispatch_target);
1563 event.flags().into()
1564 }
1565
1566 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1575 match event.event_type {
1576 TouchEventType::Down | TouchEventType::Move => {},
1577 TouchEventType::Up | TouchEventType::Cancel => {
1578 let mut active_touch_points = self.active_touch_points.borrow_mut();
1579 if let Some(index) = active_touch_points
1580 .iter()
1581 .position(|t| t.Identifier() == event.touch_id.0)
1582 {
1583 active_touch_points.swap_remove(index);
1584 self.remove_pointer_id_for_touch(event.touch_id.0);
1585 } else {
1586 warn!(
1587 "Received {:?} for a non-active touch point {}",
1588 event.event_type, event.touch_id.0
1589 );
1590 }
1591 },
1592 }
1593 }
1594
1595 fn handle_keyboard_event(
1597 &self,
1598 cx: &mut JSContext,
1599 keyboard_event: EmbedderKeyboardEvent,
1600 ) -> InputEventResult {
1601 let target = &self.target_for_events_following_focus();
1602 let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1603 cx,
1604 &self.window,
1605 keyboard_event.event.state.event_type().into(),
1606 &keyboard_event.event,
1607 );
1608
1609 let event = keyevent.upcast::<Event>();
1610
1611 event.set_composed(true);
1612
1613 event.fire(cx, target);
1614
1615 let mut flags = event.flags();
1616 if flags.contains(EventFlags::Canceled) {
1617 return flags.into();
1618 }
1619
1620 let is_character_value_key = matches!(
1626 keyboard_event.event.key,
1627 Key::Character(_) | Key::Named(NamedKey::Enter)
1628 );
1629 if keyboard_event.event.state == KeyState::Down &&
1630 is_character_value_key &&
1631 !keyboard_event.event.is_composing
1632 {
1633 let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1635 cx,
1636 &self.window,
1637 atom!("keypress"),
1638 &keyboard_event.event,
1639 );
1640 keypress_event.upcast::<Event>().set_composed(true);
1641 let event = keypress_event.upcast::<Event>();
1642 event.fire(cx, target);
1643 flags = event.flags();
1644 }
1645
1646 flags.into()
1647 }
1648
1649 fn handle_ime_event(&self, cx: &mut JSContext, event: ImeEvent) -> InputEventResult {
1650 let document = self.window.Document();
1651 let composition_event = match event {
1652 ImeEvent::Dismissed => {
1653 document.focus_handler().focus(cx, &FocusableArea::Viewport);
1654 return Default::default();
1655 },
1656 ImeEvent::Composition(composition_event) => composition_event,
1657 };
1658
1659 let focused_area = document.focus_handler().focused_area();
1664 let Some(focused_element) = focused_area.element() else {
1665 return Default::default();
1667 };
1668
1669 let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1670 let event = CompositionEvent::new(
1671 cx,
1672 &self.window,
1673 composition_event.state.event_type().into(),
1674 true,
1675 cancelable,
1676 Some(&self.window),
1677 0,
1678 DOMString::from(composition_event.data),
1679 );
1680
1681 let event = event.upcast::<Event>();
1682 event.fire(cx, focused_element.upcast());
1683 event.flags().into()
1684 }
1685
1686 fn handle_wheel_event(
1687 &self,
1688 cx: &mut JSContext,
1689 event: EmbedderWheelEvent,
1690 input_event: &ConstellationInputEvent,
1691 ) -> InputEventResult {
1692 let flags = HitTestFlags::empty();
1694 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1695 else {
1696 return Default::default();
1697 };
1698
1699 let Some(el) = hit_test_result
1700 .node
1701 .inclusive_ancestors(ShadowIncluding::Yes)
1702 .find_map(DomRoot::downcast::<Element>)
1703 else {
1704 return Default::default();
1705 };
1706
1707 let node = el.upcast::<Node>();
1708 debug!(
1709 "wheel: on {:?} at {:?}",
1710 node.debug_str(),
1711 hit_test_result.point_in_frame
1712 );
1713
1714 let event_type = "wheel".into();
1715
1716 let cancelable = EventCancelable::from(
1717 self.window
1718 .upcast::<EventTarget>()
1719 .has_non_passive_listener(&event_type) ||
1720 node.inclusive_ancestors(ShadowIncluding::Yes)
1721 .any(|target| {
1722 target
1723 .upcast::<EventTarget>()
1724 .has_non_passive_listener(&event_type)
1725 }),
1726 );
1727 let dom_event = WheelEvent::new(
1729 cx,
1730 &self.window,
1731 event_type,
1732 EventBubbles::Bubbles,
1733 cancelable,
1734 Some(&self.window),
1735 0i32,
1736 hit_test_result.point_in_frame.to_i32(),
1737 hit_test_result.point_in_frame.to_i32(),
1738 hit_test_result
1739 .point_relative_to_initial_containing_block
1740 .to_i32(),
1741 input_event.active_keyboard_modifiers,
1742 MouseButton::Primary,
1743 input_event.pressed_mouse_buttons,
1744 None,
1745 None,
1746 Finite::wrap(-event.delta.x),
1751 Finite::wrap(-event.delta.y),
1752 Finite::wrap(-event.delta.z),
1753 event.delta.mode as u32,
1754 );
1755
1756 let dom_event = dom_event.upcast::<Event>();
1757 dom_event.set_trusted(true);
1758 dom_event.set_composed(true);
1759 dom_event.fire(cx, node.upcast());
1760
1761 dom_event.flags().into()
1762 }
1763
1764 #[cfg(feature = "gamepad")]
1765 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1766 match gamepad_event {
1767 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1768 self.handle_gamepad_connect(
1769 index.0,
1770 name,
1771 bounds.axis_bounds,
1772 bounds.button_bounds,
1773 supported_haptic_effects,
1774 );
1775 },
1776 EmbedderGamepadEvent::Disconnected(index) => {
1777 self.handle_gamepad_disconnect(index.0);
1778 },
1779 EmbedderGamepadEvent::Updated(index, update_type) => {
1780 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1781 },
1782 };
1783 }
1784
1785 #[cfg(feature = "gamepad")]
1787 fn handle_gamepad_connect(
1788 &self,
1789 _index: usize,
1793 name: String,
1794 axis_bounds: (f64, f64),
1795 button_bounds: (f64, f64),
1796 supported_haptic_effects: GamepadSupportedHapticEffects,
1797 ) {
1798 let doc = self.window.Document();
1800
1801 if !doc.allowed_to_use_feature(PermissionName::Gamepad) {
1804 return;
1805 }
1806
1807 let trusted_window = Trusted::new(&*self.window);
1808
1809 self.window
1812 .upcast::<GlobalScope>()
1813 .task_manager()
1814 .gamepad_task_source()
1815 .queue(task!(gamepad_connected: move |cx| {
1816 let window = trusted_window.root();
1817
1818 let navigator = window.Navigator(cx);
1821 let selected_index = navigator.select_gamepad_index();
1822 let gamepad = Gamepad::new(
1823 cx,
1824 &window,
1825 selected_index,
1826 name,
1827 "standard".into(),
1828 axis_bounds,
1829 button_bounds,
1830 supported_haptic_effects,
1831 false,
1832 );
1833
1834 navigator.set_gamepad(selected_index as usize, Some(&gamepad));
1836
1837 if navigator.has_gamepad_gesture() {
1839 gamepad.set_exposed(true);
1841 if window.Document().is_fully_active() {
1846 gamepad.notify_event(cx, GamepadEventType::Connected);
1847 }
1848 }
1849 }));
1850 }
1851
1852 #[cfg(feature = "gamepad")]
1854 fn handle_gamepad_disconnect(&self, index: usize) {
1855 let trusted_window = Trusted::new(&*self.window);
1859 self.window
1860 .upcast::<GlobalScope>()
1861 .task_manager()
1862 .gamepad_task_source()
1863 .queue(task!(gamepad_disconnected: move |cx| {
1864 let window = trusted_window.root();
1865 let navigator = window.Navigator(cx);
1866
1867 if let Some(gamepad) = navigator.get_gamepad(index) {
1868 gamepad.update_connected(false);
1870 if gamepad.exposed() && window.Document().is_fully_active() {
1878 gamepad.notify_event(cx, GamepadEventType::Disconnected);
1879 }
1880 }
1881
1882 navigator.set_gamepad(index, None);
1886 navigator.shrink_gamepads_list();
1889 }));
1890 }
1891
1892 #[cfg(feature = "gamepad")]
1894 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1895 let trusted_window = Trusted::new(&*self.window);
1898
1899 self.window
1902 .upcast::<GlobalScope>()
1903 .task_manager()
1904 .gamepad_task_source()
1905 .queue(task!(update_gamepad_state: move |cx| {
1906 let window = trusted_window.root();
1907 let document = window.Document();
1908 document.event_handler().update_gamepad_state(cx, index, update_type);
1909 }));
1910 }
1911
1912 #[cfg(feature = "gamepad")]
1914 fn update_gamepad_state(
1915 &self,
1916 cx: &mut JSContext,
1917 gamepad_index: usize,
1918 update_type: GamepadUpdateType,
1919 ) {
1920 use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
1921 let now = *self.window.Performance(cx).Now();
1924
1925 let navigator = self.window.Navigator(cx);
1928
1929 if let Some(gamepad) = navigator.get_gamepad(gamepad_index) {
1930 gamepad.update_timestamp(now);
1932 match update_type {
1935 GamepadUpdateType::Axis(axis_index, value) => {
1936 gamepad.map_and_normalize_axes(axis_index, value);
1937 },
1938 GamepadUpdateType::Button(button_index, value) => {
1939 gamepad.map_and_normalize_buttons(button_index, value);
1940 },
1941 };
1942 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1947 navigator.set_has_gamepad_gesture(true);
1949 navigator
1951 .get_connected_gamepad()
1952 .iter()
1953 .for_each(|connected_gamepad| {
1954 connected_gamepad.set_exposed(true);
1956 connected_gamepad.update_timestamp(now);
1958 let trusted_gamepad = Trusted::new(&**connected_gamepad);
1965 if self.window.Document().is_fully_active() {
1966 self.window
1967 .upcast::<GlobalScope>()
1968 .task_manager()
1969 .gamepad_task_source()
1970 .queue(task!(fire_gamepad_connected: move |cx| {
1971 let gamepad = trusted_gamepad.root();
1972 gamepad.notify_event(cx, GamepadEventType::Connected);
1973 }));
1974 }
1975 });
1976 }
1977 }
1978 }
1979
1980 pub(crate) fn handle_editing_action(
1982 &self,
1983 cx: &mut JSContext,
1984 element: Option<DomRoot<Element>>,
1985 action: EditingActionEvent,
1986 ) -> InputEventResult {
1987 let clipboard_event_type = match action {
1988 EditingActionEvent::Copy => ClipboardEventType::Copy,
1989 EditingActionEvent::Cut => ClipboardEventType::Cut,
1990 EditingActionEvent::Paste => ClipboardEventType::Paste,
1991 };
1992
1993 let script_triggered = false;
1995
1996 let script_may_access_clipboard = false;
2000
2001 if script_triggered && !script_may_access_clipboard {
2003 return InputEventResult::empty();
2004 }
2005
2006 let clipboard_event = self.fire_clipboard_event(cx, element.clone(), clipboard_event_type);
2008
2009 let event = clipboard_event.upcast::<Event>();
2012 if !event.IsTrusted() {
2013 return event.flags().into();
2014 }
2015
2016 if event.DefaultPrevented() {
2018 let event_type = event.Type();
2019 match_domstring_ascii!(event_type,
2020
2021 "copy" => {
2022 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
2025 let drag_data_store =
2026 clipboard_data.data_store().expect("This shouldn't fail");
2027 self.write_content_to_the_clipboard(&drag_data_store);
2028 }
2029 },
2030 "cut" => {
2031 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
2034 let drag_data_store =
2035 clipboard_data.data_store().expect("This shouldn't fail");
2036 self.write_content_to_the_clipboard(&drag_data_store);
2037 }
2038
2039 self.fire_clipboard_event(cx, element, ClipboardEventType::Change);
2041 },
2042 "paste" => (),
2046 _ => (),
2047 )
2048 }
2049
2050 event.flags().into()
2053 }
2054
2055 pub(crate) fn fire_clipboard_event(
2057 &self,
2058 cx: &mut JSContext,
2059 target: Option<DomRoot<Element>>,
2060 clipboard_event_type: ClipboardEventType,
2061 ) -> DomRoot<ClipboardEvent> {
2062 let clipboard_event = ClipboardEvent::new(
2063 cx,
2064 &self.window,
2065 None,
2066 clipboard_event_type.as_str().into(),
2067 EventBubbles::Bubbles,
2068 EventCancelable::Cancelable,
2069 None,
2070 );
2071
2072 let mut drag_data_store = DragDataStore::new();
2075
2076 let trusted = true;
2080
2081 let target = target
2083 .map(DomRoot::upcast)
2084 .unwrap_or_else(|| self.target_for_events_following_focus());
2085
2086 match clipboard_event_type {
2089 ClipboardEventType::Copy | ClipboardEventType::Cut => {
2090 drag_data_store.set_mode(Mode::ReadWrite);
2092 },
2093 ClipboardEventType::Paste => {
2094 let (callback, receiver) =
2095 GenericCallback::new_blocking().expect("Could not create callback");
2096 self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
2097 self.window.webview_id(),
2098 callback,
2099 ));
2100 let text_contents = receiver
2101 .recv()
2102 .map(Result::unwrap_or_default)
2103 .unwrap_or_default();
2104
2105 drag_data_store.set_mode(Mode::ReadOnly);
2107 if trusted {
2109 let data = DOMString::from(text_contents);
2113 let type_ = DOMString::from_static("text/plain");
2114 let _ = drag_data_store.add(Kind::Text { data, type_ });
2115
2116 }
2122 },
2123 ClipboardEventType::Change => (),
2124 }
2125
2126 let clipboard_event_data = DataTransfer::new(
2128 cx,
2129 &self.window,
2130 Rc::new(RefCell::new(Some(drag_data_store))),
2131 );
2132
2133 clipboard_event.set_clipboard_data(Some(&clipboard_event_data));
2135
2136 let event = clipboard_event.upcast::<Event>();
2138 event.set_trusted(trusted);
2139
2140 event.set_composed(true);
2142
2143 event.dispatch(cx, &target, false);
2145
2146 DomRoot::from(clipboard_event)
2147 }
2148
2149 fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
2151 if drag_data_store.list_len() > 0 {
2153 self.window
2155 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
2156 for item in drag_data_store.iter_item_list() {
2158 match item {
2159 Kind::Text { data, .. } => {
2160 self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
2164 self.window.webview_id(),
2165 data.to_string(),
2166 ));
2167 },
2168 Kind::File { .. } => {
2169 },
2173 }
2174 }
2175 } else {
2176 if drag_data_store.clear_was_called {
2178 self.window
2180 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
2181 }
2184 }
2185 }
2186
2187 #[expect(unsafe_code)]
2190 pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
2191 let document = self.window.Document();
2193 if scrolled_node.is_root() {
2194 document.handle_viewport_scroll_event();
2195 } else {
2196 let node_id = node_id_from_scroll_id(scrolled_node.0 as usize);
2200 let node = unsafe {
2201 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
2202 };
2203 let Some(element) = node
2204 .inclusive_ancestors(ShadowIncluding::Yes)
2205 .find_map(DomRoot::downcast::<Element>)
2206 else {
2207 return;
2208 };
2209
2210 element.handle_scroll_event();
2211 }
2212 }
2213
2214 pub(crate) fn maybe_dispatch_simulated_click(
2220 &self,
2221 cx: &mut JSContext,
2222 node: &Node,
2223 event: &KeyboardEvent,
2224 ) -> bool {
2225 if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
2226 {
2227 return false;
2228 }
2229
2230 if node
2234 .downcast::<Element>()
2235 .and_then(Element::as_maybe_activatable)
2236 .is_none()
2237 {
2238 return false;
2239 }
2240
2241 node.fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
2242 true
2243 }
2244
2245 pub(crate) fn run_default_keyboard_event_handler(
2246 &self,
2247 cx: &mut js::context::JSContext,
2248 node: &Node,
2249 event: &KeyboardEvent,
2250 ) {
2251 if event.upcast::<Event>().type_() != atom!("keydown") {
2252 return;
2253 }
2254
2255 if self.maybe_dispatch_simulated_click(cx, node, event) {
2256 return;
2257 }
2258
2259 if self.maybe_handle_accesskey(cx, event) {
2260 return;
2261 }
2262
2263 let mut is_space = false;
2264 let scroll = match event.key() {
2265 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
2266 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
2267 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
2268 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
2269 Key::Named(NamedKey::End) => KeyboardScroll::End,
2270 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
2271 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
2272 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
2273 Key::Character(string) if &string == " " => {
2274 is_space = true;
2275 if event.modifiers().contains(Modifiers::SHIFT) {
2276 KeyboardScroll::PageUp
2277 } else {
2278 KeyboardScroll::PageDown
2279 }
2280 },
2281 Key::Named(NamedKey::Tab) => {
2282 self.window
2288 .Document()
2289 .focus_handler()
2290 .sequential_focus_navigation_via_keyboard_event(cx, event);
2291 return;
2292 },
2293 _ => return,
2294 };
2295
2296 if !event.modifiers().is_empty() && !is_space {
2297 return;
2298 }
2299
2300 self.do_keyboard_scroll(cx, scroll);
2301 }
2302
2303 pub(crate) fn do_keyboard_scroll(&self, cx: &mut JSContext, scroll: KeyboardScroll) {
2304 let scroll_axis = match scroll {
2305 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2306 _ => ScrollingBoxAxis::Y,
2307 };
2308
2309 let document = self.window.Document();
2310 let mut scrolling_box = document
2311 .focus_handler()
2312 .focused_area()
2313 .element()
2314 .or(self.most_recently_clicked_element.get().as_deref())
2315 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2316 .unwrap_or_else(|| {
2317 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2318 });
2319
2320 while !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2321 if scrolling_box.is_viewport() {
2323 break;
2324 }
2325 let parent = scrolling_box.parent().unwrap_or_else(|| {
2326 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2327 });
2328 scrolling_box = parent;
2329 }
2330
2331 let calculate_current_scroll_offset_and_delta = || {
2332 const LINE_HEIGHT: f32 = 76.0;
2333 const LINE_WIDTH: f32 = 76.0;
2334
2335 let current_scroll_offset = scrolling_box.scroll_position();
2336 (
2337 current_scroll_offset,
2338 match scroll {
2339 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2340 KeyboardScroll::End => Vector2D::new(
2341 0.0,
2342 -current_scroll_offset.y + scrolling_box.content_size().height -
2343 scrolling_box.size(cx.no_gc()).height,
2344 ),
2345 KeyboardScroll::PageDown => Vector2D::new(
2346 0.0,
2347 scrolling_box.size(cx.no_gc()).height - 2.0 * LINE_HEIGHT,
2348 ),
2349 KeyboardScroll::PageUp => Vector2D::new(
2350 0.0,
2351 2.0 * LINE_HEIGHT - scrolling_box.size(cx.no_gc()).height,
2352 ),
2353 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2354 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2355 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2356 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2357 },
2358 )
2359 };
2360
2361 let parent_pipeline = self.window.parent_info();
2365 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2366 let (_, delta) = calculate_current_scroll_offset_and_delta();
2367 self.window
2368 .paint_api()
2369 .scroll_viewport_by_delta(self.window.webview_id(), delta);
2370 }
2371
2372 if !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2375 assert!(scrolling_box.is_viewport());
2376
2377 let window_proxy = document.window().window_proxy();
2378 if let Some(iframe) = window_proxy.frame_element() {
2379 let iframe_window = iframe.owner_window();
2382 let mut realm = enter_auto_realm(cx, &*iframe_window);
2383 let cx = &mut realm;
2384 iframe_window
2385 .Document()
2386 .event_handler()
2387 .do_keyboard_scroll(cx, scroll);
2388 } else if let Some(parent_pipeline) = parent_pipeline {
2389 document.window().send_to_constellation(
2393 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2394 );
2395 };
2396 return;
2397 }
2398
2399 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2400 scrolling_box.scroll_to(cx, delta + current_scroll_offset, ScrollBehavior::Auto);
2401 }
2402
2403 fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2406 let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2407
2408 if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2409 return pointer_id;
2410 }
2411
2412 let pointer_id = self.next_touch_pointer_id.get();
2413 active_pointer_ids.insert(touch_id, pointer_id);
2414 self.next_touch_pointer_id.set(pointer_id + 1);
2415 pointer_id
2416 }
2417
2418 fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2420 self.active_pointer_ids.borrow_mut().remove(&touch_id);
2421 }
2422
2423 fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2426 self.active_pointer_ids
2429 .borrow()
2430 .values()
2431 .min()
2432 .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2433 }
2434
2435 #[allow(clippy::too_many_arguments)]
2439 fn fire_pointer_event_for_touch(
2440 &self,
2441 cx: &mut js::context::JSContext,
2442 target_element: &Element,
2443 touch: &Touch,
2444 pointer_id: i32,
2445 event_name: &str,
2446 is_primary: bool,
2447 pointer_type: &str,
2448 input_event: &ConstellationInputEvent,
2449 hit_test_result: &HitTestResult,
2450 ) {
2451 let mut targets: Vec<_> = target_element
2452 .upcast::<Node>()
2453 .inclusive_ancestors_in_flat_tree_unrooted(cx.no_gc())
2454 .map(|node| node.as_rooted())
2455 .collect();
2456
2457 if event_name == "pointerenter" {
2459 targets.reverse();
2460 }
2461
2462 for target in targets {
2463 let pointer_event = touch.to_pointer_event(
2464 cx,
2465 &self.window,
2466 event_name,
2467 pointer_id,
2468 is_primary,
2469 pointer_type,
2470 input_event.active_keyboard_modifiers,
2471 false,
2472 Some(hit_test_result.point_in_node),
2473 );
2474 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2475 }
2476 }
2477
2478 pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2479 self.access_key_handlers
2480 .borrow()
2481 .values()
2482 .any(|value| &**value == element)
2483 }
2484
2485 pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2486 self.access_key_handlers
2487 .borrow_mut()
2488 .retain(|_, value| &**value != element)
2489 }
2490
2491 pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2492 let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2493 access_key_handlers
2495 .entry(code.into())
2496 .or_insert(Dom::from_ref(element));
2497 }
2498
2499 fn maybe_handle_accesskey(
2500 &self,
2501 cx: &mut js::context::JSContext,
2502 event: &KeyboardEvent,
2503 ) -> bool {
2504 #[cfg(target_os = "macos")]
2505 let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2506 #[cfg(not(target_os = "macos"))]
2507 let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2508
2509 if event.modifiers() != access_key_modifiers {
2510 return false;
2511 }
2512
2513 let Ok(code) = Code::from_str(&event.Code().str()) else {
2514 return false;
2515 };
2516
2517 let Some(html_element) = self
2518 .access_key_handlers
2519 .borrow()
2520 .get(&code.into())
2521 .map(|html_element| html_element.as_rooted())
2522 else {
2523 return false;
2524 };
2525
2526 let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2534 return false;
2535 };
2536
2537 if command.disabled() || command.hidden() {
2538 return false;
2539 }
2540
2541 let node = html_element.upcast::<Node>();
2542 if !node.is_connected() {
2543 return false;
2544 }
2545
2546 for node in node.inclusive_ancestors_unrooted(cx.no_gc(), ShadowIncluding::Yes) {
2547 if node
2548 .downcast::<HTMLElement>()
2549 .is_some_and(|html_element| html_element.Hidden())
2550 {
2551 return false;
2552 }
2553 }
2554
2555 self.focus_and_scroll_to_element_for_key_event(cx, html_element.upcast());
2558 command.perform_action(cx);
2559 true
2560 }
2561
2562 pub(crate) fn focus_and_scroll_to_element_for_key_event(
2563 &self,
2564 cx: &mut JSContext,
2565 element: &Element,
2566 ) {
2567 element
2568 .upcast::<Node>()
2569 .run_the_focusing_steps(cx, None, FocusTrigger::Other);
2570 let scroll_axis = ScrollAxisState {
2571 position: ScrollLogicalPosition::Center,
2572 requirement: ScrollRequirement::IfNotVisible,
2573 };
2574 element.scroll_into_view_with_options(
2575 cx,
2576 ScrollBehavior::Auto,
2577 scroll_axis,
2578 scroll_axis,
2579 None,
2580 None,
2581 );
2582 }
2583
2584 pub(crate) fn is_active_pointer(&self, pointer_id: i32) -> bool {
2587 if pointer_id == PointerId::Mouse as i32 {
2588 !self.mouse_button_state.get().is_empty()
2590 } else {
2591 self.active_pointer_ids
2593 .borrow()
2594 .values()
2595 .any(|&id| id == pointer_id)
2596 }
2597 }
2598
2599 pub(crate) fn set_pending_pointer_capture(&self, pointer_id: i32, element: &Element) {
2602 self.pending_pointer_capture
2603 .borrow_mut()
2604 .insert(pointer_id, Dom::from_ref(element));
2605 }
2606
2607 pub(crate) fn clear_pending_pointer_capture(&self, pointer_id: i32) {
2610 self.pending_pointer_capture
2611 .borrow_mut()
2612 .remove(&pointer_id);
2613 }
2614
2615 pub(crate) fn has_pointer_capture(&self, pointer_id: i32, element: &Element) -> bool {
2618 self.pending_pointer_capture
2619 .borrow()
2620 .get(&pointer_id)
2621 .is_some_and(|el| &**el == element)
2622 }
2623
2624 fn get_pointer_capture_target(&self, pointer_id: i32) -> Option<DomRoot<Element>> {
2629 self.pointer_capture_target
2630 .borrow()
2631 .get(&pointer_id)
2632 .map(|el| DomRoot::from_ref(&**el))
2633 .filter(|el| el.upcast::<Node>().is_connected())
2634 }
2635
2636 fn release_disconnected_pointer_capture(
2642 &self,
2643 cx: &mut JSContext,
2644 pointer_id: i32,
2645 pointer_type: &str,
2646 is_primary: bool,
2647 ) -> bool {
2648 let capture_target = self
2649 .pointer_capture_target
2650 .borrow()
2651 .get(&pointer_id)
2652 .map(|el| DomRoot::from_ref(&**el));
2653 if let Some(capture_element) = capture_target &&
2654 !capture_element.upcast::<Node>().is_connected()
2655 {
2656 let document = self.window.Document();
2658 self.fire_pointer_capture_event_at_target(
2659 cx,
2660 "lostpointercapture",
2661 pointer_id,
2662 pointer_type,
2663 is_primary,
2664 document.upcast::<EventTarget>(),
2665 );
2666 self.pending_pointer_capture
2668 .safe_borrow_mut(cx.no_gc())
2669 .remove(&pointer_id);
2670 self.pointer_capture_target
2671 .safe_borrow_mut(cx.no_gc())
2672 .remove(&pointer_id);
2673 return true;
2674 }
2675 false
2676 }
2677
2678 fn fire_pointer_capture_event(
2681 &self,
2682 cx: &mut JSContext,
2683 event_type: &str,
2684 pointer_id: i32,
2685 pointer_type: &str,
2686 is_primary: bool,
2687 target: &Element,
2688 ) {
2689 self.fire_pointer_capture_event_at_target(
2690 cx,
2691 event_type,
2692 pointer_id,
2693 pointer_type,
2694 is_primary,
2695 target.upcast::<EventTarget>(),
2696 );
2697 }
2698
2699 fn fire_pointer_capture_event_at_target(
2701 &self,
2702 cx: &mut JSContext,
2703 event_type: &str,
2704 pointer_id: i32,
2705 pointer_type: &str,
2706 is_primary: bool,
2707 target: &EventTarget,
2708 ) {
2709 let pointer_event = PointerEvent::new(
2710 cx,
2711 &self.window,
2712 event_type.into(),
2713 EventBubbles::Bubbles,
2714 EventCancelable::NotCancelable,
2715 Some(&self.window),
2716 0,
2717 Point2D::new(0, 0),
2718 Point2D::new(0, 0),
2719 Point2D::new(0, 0),
2720 Modifiers::empty(),
2721 MouseButton::Primary,
2722 MouseButtons::empty(),
2723 None,
2724 None,
2725 pointer_id,
2726 1,
2727 1,
2728 0.0,
2729 0.0,
2730 0,
2731 0,
2732 0,
2733 PI / 2.0,
2734 0.0,
2735 DOMString::from(pointer_type),
2736 is_primary,
2737 vec![],
2738 vec![],
2739 );
2740 pointer_event.upcast::<Event>().set_composed(true);
2741 pointer_event.upcast::<Event>().fire(cx, target);
2742 }
2743
2744 #[expect(clippy::too_many_arguments)]
2748 fn fire_pointer_boundary_event(
2749 &self,
2750 cx: &mut JSContext,
2751 event_type: &str,
2752 bubbles: EventBubbles,
2753 pointer_id: i32,
2754 pointer_type: &str,
2755 is_primary: bool,
2756 target: &Element,
2757 related_target: Option<&Element>,
2758 ) {
2759 let pointer_event = PointerEvent::new(
2760 cx,
2761 &self.window,
2762 event_type.into(),
2763 bubbles,
2764 EventCancelable::NotCancelable,
2765 Some(&self.window),
2766 0,
2767 Point2D::new(0, 0),
2768 Point2D::new(0, 0),
2769 Point2D::new(0, 0),
2770 Modifiers::empty(),
2771 MouseButton::None,
2772 self.mouse_button_state.get(),
2773 related_target.map(|el| el.upcast::<EventTarget>()),
2774 None,
2775 pointer_id,
2776 1,
2777 1,
2778 0.0,
2779 0.0,
2780 0,
2781 0,
2782 0,
2783 PI / 2.0,
2784 0.0,
2785 DOMString::from(pointer_type),
2786 is_primary,
2787 vec![],
2788 vec![],
2789 );
2790 pointer_event.upcast::<Event>().set_composed(true);
2791 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2792 }
2793
2794 fn fire_pointer_capture_boundary_transition(
2799 &self,
2800 cx: &mut JSContext,
2801 pointer_id: i32,
2802 pointer_type: &str,
2803 is_primary: bool,
2804 old_target: &Element,
2805 new_target: &Element,
2806 ) {
2807 if pointer_type != "mouse" {
2808 return;
2809 }
2810 if old_target == new_target {
2811 return;
2812 }
2813 self.fire_pointer_boundary_event(
2814 cx,
2815 "pointerout",
2816 EventBubbles::Bubbles,
2817 pointer_id,
2818 pointer_type,
2819 is_primary,
2820 old_target,
2821 Some(new_target),
2822 );
2823 self.fire_pointer_boundary_event(
2824 cx,
2825 "pointerleave",
2826 EventBubbles::DoesNotBubble,
2827 pointer_id,
2828 pointer_type,
2829 is_primary,
2830 old_target,
2831 Some(new_target),
2832 );
2833 self.fire_pointer_boundary_event(
2834 cx,
2835 "pointerover",
2836 EventBubbles::Bubbles,
2837 pointer_id,
2838 pointer_type,
2839 is_primary,
2840 new_target,
2841 Some(old_target),
2842 );
2843 self.fire_pointer_boundary_event(
2844 cx,
2845 "pointerenter",
2846 EventBubbles::DoesNotBubble,
2847 pointer_id,
2848 pointer_type,
2849 is_primary,
2850 new_target,
2851 Some(old_target),
2852 );
2853 }
2854
2855 fn implicit_release_pointer_capture(
2858 &self,
2859 cx: &mut JSContext,
2860 pointer_id: i32,
2861 pointer_type: &str,
2862 is_primary: bool,
2863 ) {
2864 let capture_element = self
2865 .pointer_capture_target
2866 .borrow()
2867 .get(&pointer_id)
2868 .map(|el| DomRoot::from_ref(&**el));
2869 if let Some(capture_element) = capture_element {
2870 if capture_element.upcast::<Node>().is_connected() {
2871 self.fire_pointer_capture_event(
2872 cx,
2873 "lostpointercapture",
2874 pointer_id,
2875 pointer_type,
2876 is_primary,
2877 &capture_element,
2878 );
2879 if let Some(hover_target) = self.current_hover_target.get() {
2882 self.fire_pointer_capture_boundary_transition(
2883 cx,
2884 pointer_id,
2885 pointer_type,
2886 is_primary,
2887 &capture_element,
2888 &hover_target,
2889 );
2890 }
2891 } else {
2892 let document = self.window.Document();
2893 self.fire_pointer_capture_event_at_target(
2894 cx,
2895 "lostpointercapture",
2896 pointer_id,
2897 pointer_type,
2898 is_primary,
2899 document.upcast::<EventTarget>(),
2900 );
2901 }
2902 }
2903 self.pending_pointer_capture
2904 .safe_borrow_mut(cx.no_gc())
2905 .remove(&pointer_id);
2906 self.pointer_capture_target
2907 .safe_borrow_mut(cx.no_gc())
2908 .remove(&pointer_id);
2909 }
2910
2911 fn process_pending_pointer_capture(
2915 &self,
2916 cx: &mut JSContext,
2917 pointer_id: i32,
2918 pointer_type: &str,
2919 is_primary: bool,
2920 ) {
2921 let pending = self
2922 .pending_pointer_capture
2923 .borrow()
2924 .get(&pointer_id)
2925 .map(|el| DomRoot::from_ref(&**el));
2926 let current = self
2927 .pointer_capture_target
2928 .borrow()
2929 .get(&pointer_id)
2930 .map(|el| DomRoot::from_ref(&**el));
2931
2932 let pending_connected = pending
2935 .as_ref()
2936 .is_some_and(|el| el.upcast::<Node>().is_connected());
2937
2938 let pointer_is_active = self.is_active_pointer(pointer_id);
2941
2942 match (&pending, ¤t) {
2943 (Some(pending_el), None) if pending_connected => {
2944 if pointer_is_active {
2945 if let Some(hover_target) = self.current_hover_target.get() {
2948 self.fire_pointer_capture_boundary_transition(
2949 cx,
2950 pointer_id,
2951 pointer_type,
2952 is_primary,
2953 &hover_target,
2954 pending_el,
2955 );
2956 }
2957 self.fire_pointer_capture_event(
2958 cx,
2959 "gotpointercapture",
2960 pointer_id,
2961 pointer_type,
2962 is_primary,
2963 pending_el,
2964 );
2965 self.pointer_capture_target
2966 .safe_borrow_mut(cx.no_gc())
2967 .insert(pointer_id, Dom::from_ref(pending_el));
2968 } else {
2969 self.pending_pointer_capture
2970 .safe_borrow_mut(cx.no_gc())
2971 .remove(&pointer_id);
2972 }
2973 },
2974 (Some(pending_el), Some(current_el))
2975 if pending_connected && pending_el != current_el =>
2976 {
2977 self.fire_pointer_capture_event(
2978 cx,
2979 "lostpointercapture",
2980 pointer_id,
2981 pointer_type,
2982 is_primary,
2983 current_el,
2984 );
2985 if pointer_is_active {
2986 self.fire_pointer_capture_boundary_transition(
2987 cx,
2988 pointer_id,
2989 pointer_type,
2990 is_primary,
2991 current_el,
2992 pending_el,
2993 );
2994 self.fire_pointer_capture_event(
2995 cx,
2996 "gotpointercapture",
2997 pointer_id,
2998 pointer_type,
2999 is_primary,
3000 pending_el,
3001 );
3002 self.pointer_capture_target
3003 .safe_borrow_mut(cx.no_gc())
3004 .insert(pointer_id, Dom::from_ref(pending_el));
3005 } else {
3006 self.pending_pointer_capture
3007 .safe_borrow_mut(cx.no_gc())
3008 .remove(&pointer_id);
3009 self.pointer_capture_target
3010 .safe_borrow_mut(cx.no_gc())
3011 .remove(&pointer_id);
3012 }
3013 },
3014 (None, Some(current_el)) | (Some(_), Some(current_el)) if !pending_connected => {
3015 self.fire_pointer_capture_event(
3016 cx,
3017 "lostpointercapture",
3018 pointer_id,
3019 pointer_type,
3020 is_primary,
3021 current_el,
3022 );
3023 if let Some(hover_target) = self.current_hover_target.get() {
3026 self.fire_pointer_capture_boundary_transition(
3027 cx,
3028 pointer_id,
3029 pointer_type,
3030 is_primary,
3031 current_el,
3032 &hover_target,
3033 );
3034 }
3035 self.pointer_capture_target
3036 .safe_borrow_mut(cx.no_gc())
3037 .remove(&pointer_id);
3038 if !pending_connected {
3039 self.pending_pointer_capture
3040 .safe_borrow_mut(cx.no_gc())
3041 .remove(&pointer_id);
3042 }
3043 },
3044 _ => {},
3045 }
3046 }
3047
3048 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
3049 pub(crate) fn install_drag_gesture(&self, drag_gesture: DragGesture) {
3050 *self.drag_gesture.borrow_mut() = Some(drag_gesture);
3051 }
3052}
3053
3054pub(crate) fn character_to_code(character: char) -> Option<Code> {
3055 Some(match character.to_ascii_lowercase() {
3056 '`' => Code::Backquote,
3057 '\\' => Code::Backslash,
3058 '[' | '{' => Code::BracketLeft,
3059 ']' | '}' => Code::BracketRight,
3060 ',' | '<' => Code::Comma,
3061 '0' => Code::Digit0,
3062 '1' => Code::Digit1,
3063 '2' => Code::Digit2,
3064 '3' => Code::Digit3,
3065 '4' => Code::Digit4,
3066 '5' => Code::Digit5,
3067 '6' => Code::Digit6,
3068 '7' => Code::Digit7,
3069 '8' => Code::Digit8,
3070 '9' => Code::Digit9,
3071 '=' => Code::Equal,
3072 'a' => Code::KeyA,
3073 'b' => Code::KeyB,
3074 'c' => Code::KeyC,
3075 'd' => Code::KeyD,
3076 'e' => Code::KeyE,
3077 'f' => Code::KeyF,
3078 'g' => Code::KeyG,
3079 'h' => Code::KeyH,
3080 'i' => Code::KeyI,
3081 'j' => Code::KeyJ,
3082 'k' => Code::KeyK,
3083 'l' => Code::KeyL,
3084 'm' => Code::KeyM,
3085 'n' => Code::KeyN,
3086 'o' => Code::KeyO,
3087 'p' => Code::KeyP,
3088 'q' => Code::KeyQ,
3089 'r' => Code::KeyR,
3090 's' => Code::KeyS,
3091 't' => Code::KeyT,
3092 'u' => Code::KeyU,
3093 'v' => Code::KeyV,
3094 'w' => Code::KeyW,
3095 'x' => Code::KeyX,
3096 'y' => Code::KeyY,
3097 'z' => Code::KeyZ,
3098 '-' => Code::Minus,
3099 '.' => Code::Period,
3100 '\'' | '"' => Code::Quote,
3101 ';' => Code::Semicolon,
3102 '/' => Code::Slash,
3103 ' ' => Code::Space,
3104 _ => return None,
3105 })
3106}
3107
3108impl Element {
3109 fn inclusive_ancestor_element_in_non_ua_shadow_root(&self) -> DomRoot<Element> {
3111 if !self.upcast::<Node>().is_in_ua_widget() {
3112 return DomRoot::from_ref(self);
3113 }
3114 let Some(shadow_root) = self.containing_shadow_root() else {
3115 return DomRoot::from_ref(self);
3116 };
3117 shadow_root
3118 .Host()
3119 .inclusive_ancestor_element_in_non_ua_shadow_root()
3120 }
3121}