1use std::array::from_ref;
6use std::cell::{Cell, RefCell};
7use std::f64::consts::PI;
8use std::mem;
9use std::rc::Rc;
10use std::str::FromStr;
11use std::time::{Duration, Instant};
12
13use embedder_traits::{
14 Cursor, EditingActionEvent, EmbedderMsg, ImeEvent, InputEvent, InputEventId, InputEventOutcome,
15 InputEventResult, KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction,
16 MouseButtonEvent, MouseLeftViewportEvent, TouchEvent as EmbedderTouchEvent, TouchEventType,
17 TouchId, TouchPointerType, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
18};
19#[cfg(feature = "gamepad")]
20use embedder_traits::{
21 GamepadEvent as EmbedderGamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType,
22};
23use euclid::{Point2D, Vector2D};
24use js::context::JSContext;
25use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
26use layout_api::{ScrollContainerQueryFlags, node_id_from_scroll_id};
27use rustc_hash::FxHashMap;
28use script_bindings::cell::DomRefCell;
29use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
30use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
31use script_bindings::codegen::GenericBindings::EventBinding::EventMethods;
32use script_bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElementMethods;
33use script_bindings::codegen::GenericBindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
34use script_bindings::codegen::GenericBindings::KeyboardEventBinding::KeyboardEventMethods;
35use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
36use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
37use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
38use script_bindings::inheritance::Castable;
39use script_bindings::match_domstring_ascii;
40use script_bindings::num::Finite;
41use script_bindings::root::{Dom, DomRoot, DomSlice};
42use script_bindings::str::DOMString;
43use script_traits::ConstellationInputEvent;
44use servo_base::generic_channel::GenericCallback;
45use servo_config::pref;
46use servo_constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
47use style::Atom;
48use style_traits::CSSPixel;
49use webrender_api::ExternalScrollId;
50
51#[cfg(feature = "gamepad")]
52use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionName;
53use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
54use crate::dom::bindings::refcounted::Trusted;
55use crate::dom::bindings::root::MutNullableDom;
56use crate::dom::bindings::trace::NoTrace;
57use crate::dom::clipboardevent::ClipboardEventType;
58use crate::dom::document::FireMouseEventType;
59use crate::dom::document::focus::FocusableArea;
60use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
61#[cfg(feature = "gamepad")]
62use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
63#[cfg(feature = "gamepad")]
64use crate::dom::gamepad::gamepadevent::GamepadEventType;
65use crate::dom::inputevent::HitTestResult;
66use crate::dom::interactive_element_command::InteractiveElementCommand;
67use crate::dom::iterators::ShadowIncluding;
68use crate::dom::keyboardevent::KeyboardEvent;
69use crate::dom::node::{self, Node, NodeTraits};
70use crate::dom::pointerevent::{PointerEvent, PointerId};
71use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
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::drag_data_store::{DragDataStore, Kind, Mode};
78use crate::realms::enter_auto_realm;
79
80#[derive(Default, JSTraceable, MallocSizeOf)]
90struct ClickCountingInfo {
91 time: Option<Instant>,
92 #[no_trace]
93 point: Option<Point2D<f32, CSSPixel>>,
94 #[no_trace]
95 button: Option<MouseButton>,
96 count: usize,
97}
98
99impl ClickCountingInfo {
100 fn reset_click_count_if_necessary(
101 &mut self,
102 button: MouseButton,
103 point_in_frame: Point2D<f32, CSSPixel>,
104 ) {
105 let (Some(previous_button), Some(previous_point), Some(previous_time)) =
106 (self.button, self.point, self.time)
107 else {
108 assert_eq!(self.count, 0);
109 return;
110 };
111
112 let double_click_timeout =
113 Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
114 let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
115
116 let line = point_in_frame - previous_point;
118 let distance = (line.dot(line) as f64).sqrt();
119 if previous_button != button ||
120 Instant::now().duration_since(previous_time) > double_click_timeout ||
121 distance > double_click_distance_threshold as f64
122 {
123 self.count = 0;
124 self.time = None;
125 self.point = None;
126 }
127 }
128
129 fn increment_click_count(
130 &mut self,
131 button: MouseButton,
132 point: Point2D<f32, CSSPixel>,
133 ) -> usize {
134 self.time = Some(Instant::now());
135 self.point = Some(point);
136 self.button = Some(button);
137 self.count += 1;
138 self.count
139 }
140}
141
142#[derive(JSTraceable, MallocSizeOf)]
146#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
147pub(crate) struct DocumentEventHandler {
148 window: Dom<Window>,
150 #[no_trace]
152 #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
153 pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
154 mouse_move_event_index: DomRefCell<Option<usize>>,
156 #[no_trace]
158 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
159 coalesced_mouse_move_event_ids: DomRefCell<Vec<InputEventId>>,
160 wheel_event_index: DomRefCell<Option<usize>>,
165 #[no_trace]
167 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
168 coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
169 click_counting_info: DomRefCell<ClickCountingInfo>,
171 #[no_trace]
172 last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
173 mouse_buttons_down: Cell<u32>,
177 current_hover_target: MutNullableDom<Element>,
179 current_active_element: MutNullableDom<Element>,
182 most_recently_clicked_element: MutNullableDom<Element>,
184 #[no_trace]
186 most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
187 #[no_trace]
190 current_cursor: Cell<Option<Cursor>>,
191 active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
193 #[no_trace]
195 active_keyboard_modifiers: Cell<Modifiers>,
196 active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
198 next_touch_pointer_id: Cell<i32>,
200 access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
202 pending_pointer_capture: DomRefCell<FxHashMap<i32, Dom<Element>>>,
206 pointer_capture_target: DomRefCell<FxHashMap<i32, Dom<Element>>>,
209}
210
211impl DocumentEventHandler {
212 pub(crate) fn new(window: &Window) -> Self {
213 Self {
214 window: Dom::from_ref(window),
215 pending_input_events: Default::default(),
216 mouse_move_event_index: Default::default(),
217 coalesced_mouse_move_event_ids: Default::default(),
218 wheel_event_index: Default::default(),
219 coalesced_wheel_event_ids: Default::default(),
220 click_counting_info: Default::default(),
221 last_mouse_button_down_point: Default::default(),
222 mouse_buttons_down: Cell::new(0),
223 current_hover_target: Default::default(),
224 current_active_element: Default::default(),
225 most_recently_clicked_element: Default::default(),
226 most_recent_mousemove_point: Default::default(),
227 current_cursor: Default::default(),
228 active_touch_points: Default::default(),
229 active_keyboard_modifiers: Default::default(),
230 active_pointer_ids: Default::default(),
231 next_touch_pointer_id: Cell::new(1),
232 access_key_handlers: Default::default(),
233 pending_pointer_capture: Default::default(),
234 pointer_capture_target: Default::default(),
235 }
236 }
237
238 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
240 let mut pending_input_events = self.pending_input_events.borrow_mut();
241 if matches!(event.event.event, InputEvent::MouseMove(..)) {
242 if let Some(mouse_move_event) = self
244 .mouse_move_event_index
245 .borrow()
246 .and_then(|index| pending_input_events.get_mut(index))
247 {
248 self.coalesced_mouse_move_event_ids
249 .borrow_mut()
250 .push(mouse_move_event.event.id);
251 *mouse_move_event = event;
252 return;
253 }
254
255 *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
256 }
257
258 if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
259 if let Some(existing_constellation_wheel_event) = self
261 .wheel_event_index
262 .borrow()
263 .and_then(|index| pending_input_events.get_mut(index)) &&
264 let InputEvent::Wheel(ref mut existing_wheel_event) =
265 existing_constellation_wheel_event.event.event &&
266 existing_wheel_event.delta.mode == new_wheel_event.delta.mode
267 {
268 self.coalesced_wheel_event_ids
269 .borrow_mut()
270 .push(existing_constellation_wheel_event.event.id);
271 existing_wheel_event.delta.x += new_wheel_event.delta.x;
272 existing_wheel_event.delta.y += new_wheel_event.delta.y;
273 existing_wheel_event.delta.z += new_wheel_event.delta.z;
274 existing_wheel_event.point = new_wheel_event.point;
275 existing_constellation_wheel_event.event.id = event.event.id;
276 return;
277 }
278
279 *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
280 }
281
282 pending_input_events.push(event);
283 }
284
285 pub(crate) fn has_pending_input_events(&self) -> bool {
288 !self.pending_input_events.borrow().is_empty()
289 }
290
291 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
292 #[cfg(target_os = "macos")]
293 {
294 self.active_keyboard_modifiers
295 .get()
296 .contains(Modifiers::META)
297 }
298 #[cfg(not(target_os = "macos"))]
299 {
300 self.active_keyboard_modifiers
301 .get()
302 .contains(Modifiers::CONTROL)
303 }
304 }
305
306 pub(crate) fn handle_pending_input_events(&self, cx: &mut JSContext) {
307 debug_assert!(
308 !self.pending_input_events.borrow().is_empty(),
309 "handle_pending_input_events called with no events"
310 );
311 let mut realm = enter_auto_realm(cx, &*self.window);
312 let cx = &mut realm.current_realm();
313
314 *self.mouse_move_event_index.borrow_mut() = None;
316 *self.wheel_event_index.borrow_mut() = None;
317 let pending_input_events = mem::take(&mut *self.pending_input_events.borrow_mut());
318 let mut coalesced_mouse_move_event_ids =
319 mem::take(&mut *self.coalesced_mouse_move_event_ids.borrow_mut());
320 let mut coalesced_wheel_event_ids =
321 mem::take(&mut *self.coalesced_wheel_event_ids.borrow_mut());
322
323 let mut input_event_outcomes = Vec::with_capacity(
324 pending_input_events.len() +
325 coalesced_mouse_move_event_ids.len() +
326 coalesced_wheel_event_ids.len(),
327 );
328 for event in pending_input_events {
334 self.active_keyboard_modifiers
335 .set(event.active_keyboard_modifiers);
336 let result = match event.event.event {
337 InputEvent::MouseButton(mouse_button_event) => {
338 self.handle_native_mouse_button_event(cx, mouse_button_event, &event);
339 InputEventResult::default()
340 },
341 InputEvent::MouseMove(_) => {
342 self.handle_native_mouse_move_event(cx, &event);
343 input_event_outcomes.extend(
344 mem::take(&mut coalesced_mouse_move_event_ids)
345 .into_iter()
346 .map(|id| InputEventOutcome {
347 id,
348 result: InputEventResult::default(),
349 }),
350 );
351 InputEventResult::default()
352 },
353 InputEvent::MouseLeftViewport(mouse_leave_event) => {
354 self.handle_mouse_left_viewport_event(cx, &event, &mouse_leave_event);
355 InputEventResult::default()
356 },
357 InputEvent::Touch(touch_event) => self.handle_touch_event(cx, touch_event, &event),
358 InputEvent::Wheel(wheel_event) => {
359 let result = self.handle_wheel_event(cx, wheel_event, &event);
360 input_event_outcomes.extend(
361 mem::take(&mut coalesced_wheel_event_ids)
362 .into_iter()
363 .map(|id| InputEventOutcome { id, result }),
364 );
365 result
366 },
367 InputEvent::Keyboard(keyboard_event) => {
368 self.handle_keyboard_event(cx, keyboard_event)
369 },
370 InputEvent::Ime(ime_event) => self.handle_ime_event(cx, ime_event),
371 #[cfg(feature = "gamepad")]
372 InputEvent::Gamepad(gamepad_event) => {
373 self.handle_gamepad_event(gamepad_event);
374 InputEventResult::default()
375 },
376 InputEvent::EditingAction(editing_action_event) => {
377 self.handle_editing_action(cx, None, editing_action_event)
378 },
379 };
380
381 input_event_outcomes.push(InputEventOutcome {
382 id: event.event.id,
383 result,
384 });
385 }
386
387 self.notify_embedder_that_events_were_handled(input_event_outcomes);
388 }
389
390 fn notify_embedder_that_events_were_handled(
391 &self,
392 input_event_outcomes: Vec<InputEventOutcome>,
393 ) {
394 let trusted_window = Trusted::new(&*self.window);
397 self.window
398 .as_global_scope()
399 .task_manager()
400 .dom_manipulation_task_source()
401 .queue(task!(notify_webdriver_input_event_completed: move || {
402 let window = trusted_window.root();
403 window.send_to_embedder(
404 EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
405 }));
406 }
407
408 fn target_for_events_following_focus(&self) -> DomRoot<EventTarget> {
412 let document = self.window.Document();
413 match &*document.focus_handler().focused_area() {
414 FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
415 FocusableArea::IFrameViewport { iframe_element, .. } => {
416 DomRoot::from_ref(iframe_element.upcast())
417 },
418 FocusableArea::Viewport => document
419 .GetBody()
420 .map(DomRoot::upcast)
421 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast())),
422 }
423 }
424
425 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
426 if cursor == self.current_cursor.get() {
427 return;
428 }
429 self.current_cursor.set(cursor);
430 self.window.send_to_embedder(EmbedderMsg::SetCursor(
431 self.window.webview_id(),
432 cursor.unwrap_or_default(),
433 ));
434 }
435
436 fn handle_mouse_left_viewport_event(
437 &self,
438 cx: &mut JSContext,
439 input_event: &ConstellationInputEvent,
440 mouse_leave_event: &MouseLeftViewportEvent,
441 ) {
442 if let Some(current_hover_target) = self.current_hover_target.get() {
443 let current_hover_target = current_hover_target.upcast::<Node>();
444 for element in current_hover_target
445 .inclusive_ancestors(ShadowIncluding::Yes)
446 .filter_map(DomRoot::downcast::<Element>)
447 {
448 element.set_hover_state(false);
449 }
450
451 if let Some(hit_test_result) = self
452 .most_recent_mousemove_point
453 .get()
454 .and_then(|point| self.window.hit_test_from_point_in_viewport(point))
455 {
456 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
457 cx,
458 &self.window,
459 FireMouseEventType::Out,
460 &hit_test_result,
461 input_event,
462 );
463
464 mouse_out_event
466 .to_pointer_hover_event(cx, "pointerout")
467 .upcast::<Event>()
468 .fire(cx, current_hover_target.upcast());
469
470 mouse_out_event
471 .upcast::<Event>()
472 .fire(cx, current_hover_target.upcast());
473
474 self.handle_mouse_enter_leave_event(
475 cx,
476 current_hover_target,
477 None,
478 FireMouseEventType::Leave,
479 &hit_test_result,
480 input_event,
481 );
482 }
483 }
484
485 if !mouse_leave_event.focus_moving_to_another_iframe {
490 self.window
494 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
495 self.set_cursor(None);
496 } else {
497 self.current_cursor.set(None);
498 }
499
500 self.current_hover_target.set(None);
501 self.most_recent_mousemove_point.set(None);
502 }
503
504 fn handle_mouse_enter_leave_event(
505 &self,
506 cx: &mut JSContext,
507 event_target: &Node,
508 related_target: Option<&Node>,
509 event_type: FireMouseEventType,
510 hit_test_result: &HitTestResult,
511 input_event: &ConstellationInputEvent,
512 ) {
513 assert!(matches!(
514 event_type,
515 FireMouseEventType::Enter | FireMouseEventType::Leave
516 ));
517
518 let common_ancestor = match related_target.as_ref() {
519 Some(related_target) => event_target
520 .common_ancestor_in_flat_tree(related_target)
521 .unwrap_or_else(|| DomRoot::from_ref(event_target)),
522 None => DomRoot::from_ref(event_target),
523 };
524
525 let mut targets: Vec<_> = event_target
528 .inclusive_ancestors_in_flat_tree()
529 .take_while(|node| *node != common_ancestor)
530 .collect();
531
532 if event_type == FireMouseEventType::Enter {
535 targets.reverse();
536 }
537
538 let pointer_event_name = match event_type {
539 FireMouseEventType::Enter => "pointerenter",
540 FireMouseEventType::Leave => "pointerleave",
541 _ => unreachable!(),
542 };
543
544 for target in targets {
545 let mouse_event = MouseEvent::new_for_platform_motion_event(
546 cx,
547 &self.window,
548 event_type,
549 hit_test_result,
550 input_event,
551 );
552 mouse_event
553 .upcast::<Event>()
554 .set_related_target(related_target.as_ref().map(|target| target.upcast()));
555
556 mouse_event
558 .to_pointer_hover_event(cx, pointer_event_name)
559 .upcast::<Event>()
560 .fire(cx, target.upcast());
561
562 mouse_event.upcast::<Event>().fire(cx, target.upcast());
564 }
565 }
566
567 fn handle_native_mouse_move_event(
569 &self,
570 cx: &mut JSContext,
571 input_event: &ConstellationInputEvent,
572 ) {
573 let pointer_id = PointerId::Mouse as i32;
576 let released_disconnected =
577 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
578
579 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
584 return;
585 };
586
587 let old_mouse_move_point = self
588 .most_recent_mousemove_point
589 .replace(Some(hit_test_result.point_in_frame));
590 if old_mouse_move_point == Some(hit_test_result.point_in_frame) {
591 return;
592 }
593
594 self.set_cursor(Some(hit_test_result.cursor));
596
597 let Some(new_target) = hit_test_result
598 .node
599 .inclusive_ancestors(ShadowIncluding::Yes)
600 .find_map(DomRoot::downcast::<Element>)
601 else {
602 return;
603 };
604
605 let capture_is_active = self.get_pointer_capture_target(pointer_id).is_some();
606 let old_hover_target = self.current_hover_target.get();
607 let target_has_changed = old_hover_target
608 .as_ref()
609 .is_none_or(|old_target| *old_target != new_target);
610
611 if target_has_changed {
614 if let Some(old_target) = self.current_hover_target.get() {
616 let old_target_is_ancestor_of_new_target = old_target
617 .upcast::<Node>()
618 .is_ancestor_of(new_target.upcast::<Node>());
619
620 if !old_target_is_ancestor_of_new_target {
623 for element in old_target
624 .upcast::<Node>()
625 .inclusive_ancestors(ShadowIncluding::Yes)
626 .filter_map(DomRoot::downcast::<Element>)
627 {
628 element.set_hover_state(false);
629 }
630 }
631
632 if !capture_is_active {
633 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
634 cx,
635 &self.window,
636 FireMouseEventType::Out,
637 &hit_test_result,
638 input_event,
639 );
640 mouse_out_event
641 .upcast::<Event>()
642 .set_related_target(Some(new_target.upcast()));
643
644 mouse_out_event
646 .to_pointer_hover_event(cx, "pointerout")
647 .upcast::<Event>()
648 .fire(cx, old_target.upcast());
649
650 mouse_out_event
651 .upcast::<Event>()
652 .fire(cx, old_target.upcast());
653
654 if !old_target_is_ancestor_of_new_target {
655 let event_target = old_target.upcast::<Node>();
656 let moving_into = Some(new_target.upcast::<Node>());
657 self.handle_mouse_enter_leave_event(
658 cx,
659 event_target,
660 moving_into,
661 FireMouseEventType::Leave,
662 &hit_test_result,
663 input_event,
664 );
665 }
666 }
667 }
668
669 for element in new_target
671 .upcast::<Node>()
672 .inclusive_ancestors(ShadowIncluding::Yes)
673 .filter_map(DomRoot::downcast::<Element>)
674 {
675 element.set_hover_state(true);
676 }
677
678 if !capture_is_active {
679 let mouse_over_event = MouseEvent::new_for_platform_motion_event(
680 cx,
681 &self.window,
682 FireMouseEventType::Over,
683 &hit_test_result,
684 input_event,
685 );
686 mouse_over_event
687 .upcast::<Event>()
688 .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
689
690 mouse_over_event
692 .to_pointer_hover_event(cx, "pointerover")
693 .upcast::<Event>()
694 .dispatch(cx, new_target.upcast(), false);
695
696 mouse_over_event
697 .upcast::<Event>()
698 .dispatch(cx, new_target.upcast(), false);
699
700 let moving_from = old_hover_target
701 .as_ref()
702 .map(|old_target| old_target.upcast::<Node>());
703 let event_target = new_target.upcast::<Node>();
704 self.handle_mouse_enter_leave_event(
705 cx,
706 event_target,
707 moving_from,
708 FireMouseEventType::Enter,
709 &hit_test_result,
710 input_event,
711 );
712 }
713 }
714
715 let mouse_event = MouseEvent::new_for_platform_motion_event(
718 cx,
719 &self.window,
720 FireMouseEventType::Move,
721 &hit_test_result,
722 input_event,
723 );
724
725 let pointer_target = self
730 .get_pointer_capture_target(pointer_id)
731 .map(DomRoot::upcast::<EventTarget>)
732 .unwrap_or_else(|| DomRoot::from_ref(new_target.upcast::<EventTarget>()));
733
734 let pointer_event = mouse_event.to_pointer_event(cx, Atom::from("pointermove"));
735 pointer_event.upcast::<Event>().set_composed(true);
736 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
737
738 if !released_disconnected {
742 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
743 }
744
745 mouse_event.upcast::<Event>().fire(cx, &pointer_target);
747
748 self.update_current_hover_target_and_status(Some(new_target));
749 }
750
751 fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
752 let current_hover_target = self.current_hover_target.get();
753 if current_hover_target == new_hover_target {
754 return;
755 }
756
757 let previous_hover_target = self.current_hover_target.get();
758 self.current_hover_target.set(new_hover_target.as_deref());
759
760 if let Some(target) = self.current_hover_target.get() &&
763 let Some(anchor) = target
764 .upcast::<Node>()
765 .inclusive_ancestors(ShadowIncluding::Yes)
766 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
767 {
768 let status = anchor
769 .full_href_url_for_user_interface()
770 .map(|url| url.to_string());
771 self.window
772 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
773 return;
774 }
775
776 if previous_hover_target.is_none_or(|previous_hover_target| {
781 previous_hover_target
782 .upcast::<Node>()
783 .inclusive_ancestors(ShadowIncluding::Yes)
784 .any(|node| node.is::<HTMLAnchorElement>())
785 }) {
786 self.window
787 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
788 }
789 }
790
791 pub(crate) fn handle_refresh_cursor(&self) {
792 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
793 return;
794 };
795
796 let Some(hit_test_result) = self
797 .window
798 .hit_test_from_point_in_viewport(most_recent_mousemove_point)
799 else {
800 return;
801 };
802
803 self.set_cursor(Some(hit_test_result.cursor));
804 }
805
806 fn set_active_element(&self, original_target: &Element) {
807 let find_element_for_activation = |element: &Element| {
808 let node: &Node = element.upcast();
809 if node.is_in_ua_widget() &&
810 let Some(containing_shadow_root) = node.containing_shadow_root()
811 {
812 return containing_shadow_root.Host();
813 }
814
815 if node.type_id() ==
817 NodeTypeId::Element(ElementTypeId::HTMLElement(
818 HTMLElementTypeId::HTMLLabelElement,
819 ))
820 {
821 let label = element.downcast::<HTMLLabelElement>().unwrap();
822 if let Some(control) = label.GetControl() {
823 return DomRoot::from_ref(control.upcast::<Element>());
824 }
825 }
826
827 DomRoot::from_ref(element)
828 };
829 let element_for_activation = find_element_for_activation(original_target);
830
831 if let Some(currently_active_element) = self.current_active_element.get() {
834 if currently_active_element == element_for_activation {
835 return;
836 }
837 self.unset_active_element();
838 }
839
840 element_for_activation.set_active_state(true);
841 self.current_active_element
842 .set(Some(&*element_for_activation));
843 }
844
845 fn unset_active_element(&self) {
846 if let Some(active_element) = self.current_active_element.take() {
847 active_element.set_active_state(false);
848 }
849 }
850
851 fn handle_native_mouse_button_event(
854 &self,
855 cx: &mut JSContext,
856 event: MouseButtonEvent,
857 input_event: &ConstellationInputEvent,
858 ) {
859 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
861 return;
862 };
863
864 debug!(
865 "{:?}: at {:?}",
866 event.action, hit_test_result.point_in_frame
867 );
868
869 let document = self.window.Document();
872 if event.action == MouseButtonAction::Down {
873 document
874 .focus_handler()
875 .set_sequential_focus_navigation_starting_point(&hit_test_result.node);
876 }
877
878 let Some(element) = hit_test_result
879 .node
880 .inclusive_ancestors(ShadowIncluding::Yes)
881 .find_map(DomRoot::downcast::<Element>)
882 else {
883 return;
884 };
885
886 let node = element.upcast::<Node>();
887 debug!("{:?} on {:?}", event.action, node.debug_str());
888
889 if event.button == MouseButton::Left {
894 if event.action == MouseButtonAction::Down {
895 self.set_active_element(&element);
896 }
897 if event.action == MouseButtonAction::Up {
898 self.unset_active_element();
899 }
900 }
901
902 if element.is_actually_disabled() {
906 return;
907 }
908
909 let mouse_event_type = match event.action {
910 embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
911 embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
912 };
913
914 if event.action == MouseButtonAction::Down {
921 self.click_counting_info
922 .borrow_mut()
923 .reset_click_count_if_necessary(event.button, hit_test_result.point_in_frame);
924 }
925
926 let mouse_event = MouseEvent::for_platform_button_event(
927 cx,
928 mouse_event_type,
929 event,
930 input_event.pressed_mouse_buttons,
931 &self.window,
932 &hit_test_result,
933 input_event.active_keyboard_modifiers,
934 self.click_counting_info.borrow().count + 1,
935 );
936
937 match event.action {
938 MouseButtonAction::Down => {
939 self.last_mouse_button_down_point
940 .set(Some(hit_test_result.point_in_frame));
941
942 let mouse_buttons_down = self.mouse_buttons_down.get();
944 let pointer_event_name = if mouse_buttons_down == 0 {
945 "pointerdown".into()
950 } else {
951 "pointermove".into()
957 };
958 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
959
960 let pointer_id = PointerId::Mouse as i32;
962
963 let released_disconnected =
965 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
966
967 let pointer_target = self
969 .get_pointer_capture_target(pointer_id)
970 .map(DomRoot::upcast::<EventTarget>)
971 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
972
973 self.mouse_buttons_down.set(mouse_buttons_down + 1);
975
976 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
977
978 if !released_disconnected {
982 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
983 }
984
985 let result = mouse_event
987 .upcast::<Event>()
988 .dispatch(cx, node.upcast(), false);
989
990 if result {
993 document
997 .focus_handler()
998 .focus(cx, node.find_click_focusable_area());
999 }
1000
1001 if let MouseButton::Right = event.button {
1004 self.maybe_show_context_menu(cx, node.upcast(), &hit_test_result, input_event);
1005 }
1006 },
1007 MouseButtonAction::Up => {
1009 let mouse_buttons_down = self.mouse_buttons_down.get();
1011 let pointer_event_name = if mouse_buttons_down == 1 {
1012 "pointerup".into()
1017 } else {
1018 "pointermove".into()
1024 };
1025 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1026
1027 let pointer_id = PointerId::Mouse as i32;
1029
1030 let released_disconnected =
1032 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1033
1034 let pointer_target = self
1036 .get_pointer_capture_target(pointer_id)
1037 .map(DomRoot::upcast::<EventTarget>)
1038 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1039
1040 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1041
1042 self.mouse_buttons_down
1045 .set(mouse_buttons_down.saturating_sub(1));
1046
1047 if !released_disconnected {
1051 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1052 }
1053
1054 if mouse_buttons_down == 1 {
1056 self.implicit_release_pointer_capture(cx, pointer_id, "mouse", true);
1057 }
1058
1059 mouse_event
1061 .upcast::<Event>()
1062 .dispatch(cx, node.upcast(), false);
1063
1064 self.click_counting_info
1068 .borrow_mut()
1069 .increment_click_count(event.button, hit_test_result.point_in_frame);
1070
1071 self.maybe_trigger_click_for_mouse_button_down_event(
1072 cx,
1073 event,
1074 input_event,
1075 &hit_test_result,
1076 &element,
1077 );
1078 },
1079 }
1080 }
1081
1082 fn maybe_trigger_click_for_mouse_button_down_event(
1085 &self,
1086 cx: &mut JSContext,
1087 event: MouseButtonEvent,
1088 input_event: &ConstellationInputEvent,
1089 hit_test_result: &HitTestResult,
1090 element: &Element,
1091 ) {
1092 if event.button != MouseButton::Left {
1093 return;
1094 }
1095
1096 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
1097 return;
1098 };
1099
1100 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
1101 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
1102 if distance > maximum_click_distance {
1103 return;
1104 }
1105
1106 let element = &element.inclusive_ancestor_element_in_non_ua_shadow_root();
1110 self.most_recently_clicked_element.set(Some(element));
1111
1112 let click_count = self.click_counting_info.borrow().count;
1113 element.set_click_in_progress(true);
1114 MouseEvent::for_platform_button_event(
1115 cx,
1116 atom!("click"),
1117 event,
1118 input_event.pressed_mouse_buttons,
1119 &self.window,
1120 hit_test_result,
1121 input_event.active_keyboard_modifiers,
1122 click_count,
1123 )
1124 .upcast::<Event>()
1125 .dispatch(cx, element.upcast(), false);
1126 element.set_click_in_progress(false);
1127
1128 if click_count.is_multiple_of(2) {
1137 MouseEvent::for_platform_button_event(
1138 cx,
1139 Atom::from("dblclick"),
1140 event,
1141 input_event.pressed_mouse_buttons,
1142 &self.window,
1143 hit_test_result,
1144 input_event.active_keyboard_modifiers,
1145 2,
1146 )
1147 .upcast::<Event>()
1148 .dispatch(cx, element.upcast(), false);
1149 }
1150 }
1151
1152 fn maybe_show_context_menu(
1154 &self,
1155 cx: &mut js::context::JSContext,
1156 target: &EventTarget,
1157 hit_test_result: &HitTestResult,
1158 input_event: &ConstellationInputEvent,
1159 ) {
1160 let menu_event = PointerEvent::new(
1162 cx,
1163 &self.window, "contextmenu".into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
1170 hit_test_result.point_in_frame.to_i32(),
1171 hit_test_result
1172 .point_relative_to_initial_containing_block
1173 .to_i32(),
1174 input_event.active_keyboard_modifiers,
1175 2i16, input_event.pressed_mouse_buttons,
1177 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![], );
1194 menu_event.upcast::<Event>().set_composed(true);
1195
1196 let result = menu_event.upcast::<Event>().fire(cx, target);
1198
1199 if result {
1201 self.window
1202 .Document()
1203 .embedder_controls()
1204 .show_context_menu(hit_test_result);
1205 };
1206 }
1207
1208 fn handle_touch_event(
1209 &self,
1210 cx: &mut JSContext,
1211 event: EmbedderTouchEvent,
1212 input_event: &ConstellationInputEvent,
1213 ) -> InputEventResult {
1214 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1216 self.update_active_touch_points_when_early_return(event);
1217 return Default::default();
1218 };
1219
1220 let TouchId(identifier) = event.touch_id;
1221
1222 let Some(element) = hit_test_result
1223 .node
1224 .inclusive_ancestors(ShadowIncluding::Yes)
1225 .find_map(DomRoot::downcast::<Element>)
1226 else {
1227 self.update_active_touch_points_when_early_return(event);
1228 return Default::default();
1229 };
1230
1231 let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1232 let window = &*self.window;
1233
1234 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1235 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1236 let page_x =
1237 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1238 let page_y =
1239 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1240
1241 let pointer_touch = Touch::new(
1243 cx,
1244 window,
1245 identifier,
1246 ¤t_target,
1247 client_x,
1248 client_y, client_x,
1250 client_y,
1251 page_x,
1252 page_y,
1253 );
1254
1255 let pointer_event_name = match event.event_type {
1257 TouchEventType::Down => "pointerdown",
1258 TouchEventType::Move => "pointermove",
1259 TouchEventType::Up => "pointerup",
1260 TouchEventType::Cancel => "pointercancel",
1261 };
1262
1263 let pointer_type = match event.pointer_type {
1265 TouchPointerType::Pen => "pen",
1266 TouchPointerType::Touch => "touch",
1267 };
1268
1269 let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1271 let is_primary = self.is_primary_pointer(pointer_id);
1272
1273 if matches!(event.event_type, TouchEventType::Down) {
1276 let pointer_over = pointer_touch.to_pointer_event(
1278 cx,
1279 window,
1280 "pointerover",
1281 pointer_id,
1282 is_primary,
1283 pointer_type,
1284 input_event.active_keyboard_modifiers,
1285 true, Some(hit_test_result.point_in_node),
1287 );
1288 pointer_over.upcast::<Event>().fire(cx, ¤t_target);
1289
1290 self.fire_pointer_event_for_touch(
1292 cx,
1293 &element,
1294 &pointer_touch,
1295 pointer_id,
1296 "pointerenter",
1297 is_primary,
1298 pointer_type,
1299 input_event,
1300 &hit_test_result,
1301 );
1302 }
1303
1304 let released_disconnected = if matches!(event.event_type, TouchEventType::Cancel) {
1308 false
1309 } else {
1310 self.release_disconnected_pointer_capture(cx, pointer_id, pointer_type, is_primary)
1311 };
1312
1313 let pointer_target = self
1315 .get_pointer_capture_target(pointer_id)
1316 .map(DomRoot::upcast::<EventTarget>)
1317 .unwrap_or_else(|| current_target.clone());
1318
1319 let pointer_event = pointer_touch.to_pointer_event(
1320 cx,
1321 window,
1322 pointer_event_name,
1323 pointer_id,
1324 is_primary,
1325 pointer_type,
1326 input_event.active_keyboard_modifiers,
1327 event.is_cancelable(),
1328 Some(hit_test_result.point_in_node),
1329 );
1330 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1331
1332 if !released_disconnected && !matches!(event.event_type, TouchEventType::Cancel) {
1338 self.process_pending_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1339 }
1340
1341 if matches!(
1344 event.event_type,
1345 TouchEventType::Up | TouchEventType::Cancel
1346 ) {
1347 self.implicit_release_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1348 }
1349
1350 if matches!(
1353 event.event_type,
1354 TouchEventType::Up | TouchEventType::Cancel
1355 ) {
1356 let pointer_out = pointer_touch.to_pointer_event(
1358 cx,
1359 window,
1360 "pointerout",
1361 pointer_id,
1362 is_primary,
1363 pointer_type,
1364 input_event.active_keyboard_modifiers,
1365 true, Some(hit_test_result.point_in_node),
1367 );
1368 pointer_out.upcast::<Event>().fire(cx, ¤t_target);
1369
1370 self.fire_pointer_event_for_touch(
1372 cx,
1373 &element,
1374 &pointer_touch,
1375 pointer_id,
1376 "pointerleave",
1377 is_primary,
1378 pointer_type,
1379 input_event,
1380 &hit_test_result,
1381 );
1382 }
1383
1384 let (touch_dispatch_target, changed_touch) = match event.event_type {
1385 TouchEventType::Down => {
1386 self.active_touch_points
1388 .borrow_mut()
1389 .push(Dom::from_ref(&*pointer_touch));
1390 self.set_active_element(&element);
1391 (current_target, pointer_touch)
1392 },
1393 _ => {
1394 let mut active_touch_points = self.active_touch_points.borrow_mut();
1400 let Some(index) = active_touch_points
1401 .iter()
1402 .position(|point| point.Identifier() == identifier)
1403 else {
1404 warn!("No active touch point for {:?}", event.event_type);
1405 return Default::default();
1406 };
1407 let original_target = active_touch_points[index].Target();
1409
1410 let touch_with_touchstart_target = Touch::new(
1411 cx,
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 );
1422
1423 match event.event_type {
1425 TouchEventType::Move => {
1426 active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1427 },
1428 TouchEventType::Up | TouchEventType::Cancel => {
1429 active_touch_points.swap_remove(index);
1430 self.remove_pointer_id_for_touch(identifier);
1431 self.unset_active_element();
1432 },
1433 TouchEventType::Down => unreachable!("Should have been handled above"),
1434 }
1435 (original_target, touch_with_touchstart_target)
1436 },
1437 };
1438
1439 rooted_vec!(let mut target_touches);
1440 target_touches.extend(
1441 self.active_touch_points
1442 .borrow()
1443 .iter()
1444 .filter(|touch| touch.Target() == touch_dispatch_target)
1445 .cloned(),
1446 );
1447
1448 let event_name = match event.event_type {
1449 TouchEventType::Down => "touchstart",
1450 TouchEventType::Move => "touchmove",
1451 TouchEventType::Up => "touchend",
1452 TouchEventType::Cancel => "touchcancel",
1453 };
1454
1455 let touches = TouchList::new(cx, window, self.active_touch_points.borrow().r());
1456 let changed_touches = TouchList::new(cx, window, from_ref(&&*changed_touch));
1457 let target_touches = TouchList::new(cx, window, target_touches.r());
1458
1459 let touch_event = TouchEvent::new(
1460 cx,
1461 window,
1462 event_name.into(),
1463 EventBubbles::Bubbles,
1464 EventCancelable::from(event.is_cancelable()),
1465 EventComposed::Composed,
1466 Some(window),
1467 0i32,
1468 &touches,
1469 &changed_touches,
1470 &target_touches,
1471 false,
1473 false,
1474 false,
1475 false,
1476 );
1477 let event = touch_event.upcast::<Event>();
1478 event.fire(cx, &touch_dispatch_target);
1479 event.flags().into()
1480 }
1481
1482 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1491 match event.event_type {
1492 TouchEventType::Down | TouchEventType::Move => {},
1493 TouchEventType::Up | TouchEventType::Cancel => {
1494 let mut active_touch_points = self.active_touch_points.borrow_mut();
1495 if let Some(index) = active_touch_points
1496 .iter()
1497 .position(|t| t.Identifier() == event.touch_id.0)
1498 {
1499 active_touch_points.swap_remove(index);
1500 self.remove_pointer_id_for_touch(event.touch_id.0);
1501 } else {
1502 warn!(
1503 "Received {:?} for a non-active touch point {}",
1504 event.event_type, event.touch_id.0
1505 );
1506 }
1507 },
1508 }
1509 }
1510
1511 fn handle_keyboard_event(
1513 &self,
1514 cx: &mut JSContext,
1515 keyboard_event: EmbedderKeyboardEvent,
1516 ) -> InputEventResult {
1517 let target = &self.target_for_events_following_focus();
1518 let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1519 cx,
1520 &self.window,
1521 keyboard_event.event.state.event_type().into(),
1522 &keyboard_event.event,
1523 );
1524
1525 let event = keyevent.upcast::<Event>();
1526
1527 event.set_composed(true);
1528
1529 event.fire(cx, target);
1530
1531 let mut flags = event.flags();
1532 if flags.contains(EventFlags::Canceled) {
1533 return flags.into();
1534 }
1535
1536 let is_character_value_key = matches!(
1542 keyboard_event.event.key,
1543 Key::Character(_) | Key::Named(NamedKey::Enter)
1544 );
1545 if keyboard_event.event.state == KeyState::Down &&
1546 is_character_value_key &&
1547 !keyboard_event.event.is_composing
1548 {
1549 let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1551 cx,
1552 &self.window,
1553 atom!("keypress"),
1554 &keyboard_event.event,
1555 );
1556 keypress_event.upcast::<Event>().set_composed(true);
1557 let event = keypress_event.upcast::<Event>();
1558 event.fire(cx, target);
1559 flags = event.flags();
1560 }
1561
1562 flags.into()
1563 }
1564
1565 fn handle_ime_event(&self, cx: &mut JSContext, event: ImeEvent) -> InputEventResult {
1566 let document = self.window.Document();
1567 let composition_event = match event {
1568 ImeEvent::Dismissed => {
1569 document.focus_handler().focus(cx, FocusableArea::Viewport);
1570 return Default::default();
1571 },
1572 ImeEvent::Composition(composition_event) => composition_event,
1573 };
1574
1575 let focused_area = document.focus_handler().focused_area();
1580 let Some(focused_element) = focused_area.element() else {
1581 return Default::default();
1583 };
1584
1585 let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1586 let event = CompositionEvent::new(
1587 cx,
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 );
1596
1597 let event = event.upcast::<Event>();
1598 event.fire(cx, focused_element.upcast());
1599 event.flags().into()
1600 }
1601
1602 fn handle_wheel_event(
1603 &self,
1604 cx: &mut JSContext,
1605 event: EmbedderWheelEvent,
1606 input_event: &ConstellationInputEvent,
1607 ) -> InputEventResult {
1608 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1610 return Default::default();
1611 };
1612
1613 let Some(el) = hit_test_result
1614 .node
1615 .inclusive_ancestors(ShadowIncluding::Yes)
1616 .find_map(DomRoot::downcast::<Element>)
1617 else {
1618 return Default::default();
1619 };
1620
1621 let node = el.upcast::<Node>();
1622 debug!(
1623 "wheel: on {:?} at {:?}",
1624 node.debug_str(),
1625 hit_test_result.point_in_frame
1626 );
1627
1628 let event_type = "wheel".into();
1629
1630 let cancelable = EventCancelable::from(
1631 self.window
1632 .upcast::<EventTarget>()
1633 .has_non_passive_listener(&event_type) ||
1634 node.inclusive_ancestors(ShadowIncluding::Yes)
1635 .any(|target| {
1636 target
1637 .upcast::<EventTarget>()
1638 .has_non_passive_listener(&event_type)
1639 }),
1640 );
1641 let dom_event = WheelEvent::new(
1643 cx,
1644 &self.window,
1645 event_type,
1646 EventBubbles::Bubbles,
1647 cancelable,
1648 Some(&self.window),
1649 0i32,
1650 hit_test_result.point_in_frame.to_i32(),
1651 hit_test_result.point_in_frame.to_i32(),
1652 hit_test_result
1653 .point_relative_to_initial_containing_block
1654 .to_i32(),
1655 input_event.active_keyboard_modifiers,
1656 0i16,
1657 input_event.pressed_mouse_buttons,
1658 None,
1659 None,
1660 Finite::wrap(-event.delta.x),
1665 Finite::wrap(-event.delta.y),
1666 Finite::wrap(-event.delta.z),
1667 event.delta.mode as u32,
1668 );
1669
1670 let dom_event = dom_event.upcast::<Event>();
1671 dom_event.set_trusted(true);
1672 dom_event.set_composed(true);
1673 dom_event.fire(cx, node.upcast());
1674
1675 dom_event.flags().into()
1676 }
1677
1678 #[cfg(feature = "gamepad")]
1679 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1680 match gamepad_event {
1681 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1682 self.handle_gamepad_connect(
1683 index.0,
1684 name,
1685 bounds.axis_bounds,
1686 bounds.button_bounds,
1687 supported_haptic_effects,
1688 );
1689 },
1690 EmbedderGamepadEvent::Disconnected(index) => {
1691 self.handle_gamepad_disconnect(index.0);
1692 },
1693 EmbedderGamepadEvent::Updated(index, update_type) => {
1694 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1695 },
1696 };
1697 }
1698
1699 #[cfg(feature = "gamepad")]
1701 fn handle_gamepad_connect(
1702 &self,
1703 _index: usize,
1707 name: String,
1708 axis_bounds: (f64, f64),
1709 button_bounds: (f64, f64),
1710 supported_haptic_effects: GamepadSupportedHapticEffects,
1711 ) {
1712 let doc = self.window.Document();
1714
1715 if !doc.allowed_to_use_feature(PermissionName::Gamepad) {
1718 return;
1719 }
1720
1721 let trusted_window = Trusted::new(&*self.window);
1722
1723 self.window
1726 .upcast::<GlobalScope>()
1727 .task_manager()
1728 .gamepad_task_source()
1729 .queue(task!(gamepad_connected: move |cx| {
1730 let window = trusted_window.root();
1731
1732 let navigator = window.Navigator(cx);
1735 let selected_index = navigator.select_gamepad_index();
1736 let gamepad = Gamepad::new(
1737 cx,
1738 &window,
1739 selected_index,
1740 name,
1741 "standard".into(),
1742 axis_bounds,
1743 button_bounds,
1744 supported_haptic_effects,
1745 false,
1746 );
1747
1748 navigator.set_gamepad(selected_index as usize, Some(&gamepad));
1750
1751 if navigator.has_gamepad_gesture() {
1753 gamepad.set_exposed(true);
1755 if window.Document().is_fully_active() {
1760 gamepad.notify_event(cx, GamepadEventType::Connected);
1761 }
1762 }
1763 }));
1764 }
1765
1766 #[cfg(feature = "gamepad")]
1768 fn handle_gamepad_disconnect(&self, index: usize) {
1769 let trusted_window = Trusted::new(&*self.window);
1773 self.window
1774 .upcast::<GlobalScope>()
1775 .task_manager()
1776 .gamepad_task_source()
1777 .queue(task!(gamepad_disconnected: move |cx| {
1778 let window = trusted_window.root();
1779 let navigator = window.Navigator(cx);
1780
1781 if let Some(gamepad) = navigator.get_gamepad(index) {
1782 gamepad.update_connected(false);
1784 if gamepad.exposed() && window.Document().is_fully_active() {
1792 gamepad.notify_event(cx, GamepadEventType::Disconnected);
1793 }
1794 }
1795
1796 navigator.set_gamepad(index, None);
1800 navigator.shrink_gamepads_list();
1803 }));
1804 }
1805
1806 #[cfg(feature = "gamepad")]
1808 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1809 let trusted_window = Trusted::new(&*self.window);
1812
1813 self.window
1816 .upcast::<GlobalScope>()
1817 .task_manager()
1818 .gamepad_task_source()
1819 .queue(task!(update_gamepad_state: move |cx| {
1820 let window = trusted_window.root();
1821 let document = window.Document();
1822 document.event_handler().update_gamepad_state(cx, index, update_type);
1823 }));
1824 }
1825
1826 #[cfg(feature = "gamepad")]
1828 fn update_gamepad_state(
1829 &self,
1830 cx: &mut JSContext,
1831 gamepad_index: usize,
1832 update_type: GamepadUpdateType,
1833 ) {
1834 use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
1835 let now = *self.window.Performance(cx).Now();
1838
1839 let navigator = self.window.Navigator(cx);
1842
1843 if let Some(gamepad) = navigator.get_gamepad(gamepad_index) {
1844 gamepad.update_timestamp(now);
1846 match update_type {
1849 GamepadUpdateType::Axis(axis_index, value) => {
1850 gamepad.map_and_normalize_axes(axis_index, value);
1851 },
1852 GamepadUpdateType::Button(button_index, value) => {
1853 gamepad.map_and_normalize_buttons(button_index, value);
1854 },
1855 };
1856 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1861 navigator.set_has_gamepad_gesture(true);
1863 navigator
1865 .get_connected_gamepad()
1866 .iter()
1867 .for_each(|connected_gamepad| {
1868 connected_gamepad.set_exposed(true);
1870 connected_gamepad.update_timestamp(now);
1872 let trusted_gamepad = Trusted::new(&**connected_gamepad);
1879 if self.window.Document().is_fully_active() {
1880 self.window
1881 .upcast::<GlobalScope>()
1882 .task_manager()
1883 .gamepad_task_source()
1884 .queue(task!(fire_gamepad_connected: move |cx| {
1885 let gamepad = trusted_gamepad.root();
1886 gamepad.notify_event(cx, GamepadEventType::Connected);
1887 }));
1888 }
1889 });
1890 }
1891 }
1892 }
1893
1894 pub(crate) fn handle_editing_action(
1896 &self,
1897 cx: &mut JSContext,
1898 element: Option<DomRoot<Element>>,
1899 action: EditingActionEvent,
1900 ) -> InputEventResult {
1901 let clipboard_event_type = match action {
1902 EditingActionEvent::Copy => ClipboardEventType::Copy,
1903 EditingActionEvent::Cut => ClipboardEventType::Cut,
1904 EditingActionEvent::Paste => ClipboardEventType::Paste,
1905 };
1906
1907 let script_triggered = false;
1909
1910 let script_may_access_clipboard = false;
1914
1915 if script_triggered && !script_may_access_clipboard {
1917 return InputEventResult::empty();
1918 }
1919
1920 let clipboard_event = self.fire_clipboard_event(cx, element.clone(), clipboard_event_type);
1922
1923 let event = clipboard_event.upcast::<Event>();
1926 if !event.IsTrusted() {
1927 return event.flags().into();
1928 }
1929
1930 if event.DefaultPrevented() {
1932 let event_type = event.Type();
1933 match_domstring_ascii!(event_type,
1934
1935 "copy" => {
1936 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1939 let drag_data_store =
1940 clipboard_data.data_store().expect("This shouldn't fail");
1941 self.write_content_to_the_clipboard(&drag_data_store);
1942 }
1943 },
1944 "cut" => {
1945 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1948 let drag_data_store =
1949 clipboard_data.data_store().expect("This shouldn't fail");
1950 self.write_content_to_the_clipboard(&drag_data_store);
1951 }
1952
1953 self.fire_clipboard_event(cx, element, ClipboardEventType::Change);
1955 },
1956 "paste" => (),
1960 _ => (),
1961 )
1962 }
1963
1964 event.flags().into()
1967 }
1968
1969 pub(crate) fn fire_clipboard_event(
1971 &self,
1972 cx: &mut JSContext,
1973 target: Option<DomRoot<Element>>,
1974 clipboard_event_type: ClipboardEventType,
1975 ) -> DomRoot<ClipboardEvent> {
1976 let clipboard_event = ClipboardEvent::new(
1977 cx,
1978 &self.window,
1979 None,
1980 clipboard_event_type.as_str().into(),
1981 EventBubbles::Bubbles,
1982 EventCancelable::Cancelable,
1983 None,
1984 );
1985
1986 let mut drag_data_store = DragDataStore::new();
1989
1990 let trusted = true;
1994
1995 let target = target
1997 .map(DomRoot::upcast)
1998 .unwrap_or_else(|| self.target_for_events_following_focus());
1999
2000 match clipboard_event_type {
2003 ClipboardEventType::Copy | ClipboardEventType::Cut => {
2004 drag_data_store.set_mode(Mode::ReadWrite);
2006 },
2007 ClipboardEventType::Paste => {
2008 let (callback, receiver) =
2009 GenericCallback::new_blocking().expect("Could not create callback");
2010 self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
2011 self.window.webview_id(),
2012 callback,
2013 ));
2014 let text_contents = receiver
2015 .recv()
2016 .map(Result::unwrap_or_default)
2017 .unwrap_or_default();
2018
2019 drag_data_store.set_mode(Mode::ReadOnly);
2021 if trusted {
2023 let data = DOMString::from(text_contents);
2027 let type_ = DOMString::from("text/plain");
2028 let _ = drag_data_store.add(Kind::Text { data, type_ });
2029
2030 }
2036 },
2037 ClipboardEventType::Change => (),
2038 }
2039
2040 let clipboard_event_data = DataTransfer::new(
2042 cx,
2043 &self.window,
2044 Rc::new(RefCell::new(Some(drag_data_store))),
2045 );
2046
2047 clipboard_event.set_clipboard_data(Some(&clipboard_event_data));
2049
2050 let event = clipboard_event.upcast::<Event>();
2052 event.set_trusted(trusted);
2053
2054 event.set_composed(true);
2056
2057 event.dispatch(cx, &target, false);
2059
2060 DomRoot::from(clipboard_event)
2061 }
2062
2063 fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
2065 if drag_data_store.list_len() > 0 {
2067 self.window
2069 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
2070 for item in drag_data_store.iter_item_list() {
2072 match item {
2073 Kind::Text { data, .. } => {
2074 self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
2078 self.window.webview_id(),
2079 data.to_string(),
2080 ));
2081 },
2082 Kind::File { .. } => {
2083 },
2087 }
2088 }
2089 } else {
2090 if drag_data_store.clear_was_called {
2092 self.window
2094 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
2095 }
2098 }
2099 }
2100
2101 #[expect(unsafe_code)]
2104 pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
2105 let document = self.window.Document();
2107 if scrolled_node.is_root() {
2108 document.handle_viewport_scroll_event();
2109 } else {
2110 let node_id = node_id_from_scroll_id(scrolled_node.0 as usize);
2114 let node = unsafe {
2115 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
2116 };
2117 let Some(element) = node
2118 .inclusive_ancestors(ShadowIncluding::Yes)
2119 .find_map(DomRoot::downcast::<Element>)
2120 else {
2121 return;
2122 };
2123
2124 element.handle_scroll_event();
2125 }
2126 }
2127
2128 pub(crate) fn maybe_dispatch_simulated_click(
2134 &self,
2135 cx: &mut JSContext,
2136 node: &Node,
2137 event: &KeyboardEvent,
2138 ) -> bool {
2139 if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
2140 {
2141 return false;
2142 }
2143
2144 if node
2148 .downcast::<Element>()
2149 .and_then(Element::as_maybe_activatable)
2150 .is_none()
2151 {
2152 return false;
2153 }
2154
2155 node.fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
2156 true
2157 }
2158
2159 pub(crate) fn run_default_keyboard_event_handler(
2160 &self,
2161 cx: &mut js::context::JSContext,
2162 node: &Node,
2163 event: &KeyboardEvent,
2164 ) {
2165 if event.upcast::<Event>().type_() != atom!("keydown") {
2166 return;
2167 }
2168
2169 if self.maybe_dispatch_simulated_click(cx, node, event) {
2170 return;
2171 }
2172
2173 if self.maybe_handle_accesskey(cx, event) {
2174 return;
2175 }
2176
2177 let mut is_space = false;
2178 let scroll = match event.key() {
2179 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
2180 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
2181 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
2182 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
2183 Key::Named(NamedKey::End) => KeyboardScroll::End,
2184 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
2185 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
2186 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
2187 Key::Character(string) if &string == " " => {
2188 is_space = true;
2189 if event.modifiers().contains(Modifiers::SHIFT) {
2190 KeyboardScroll::PageUp
2191 } else {
2192 KeyboardScroll::PageDown
2193 }
2194 },
2195 Key::Named(NamedKey::Tab) => {
2196 self.window
2202 .Document()
2203 .focus_handler()
2204 .sequential_focus_navigation_via_keyboard_event(cx, event);
2205 return;
2206 },
2207 _ => return,
2208 };
2209
2210 if !event.modifiers().is_empty() && !is_space {
2211 return;
2212 }
2213
2214 self.do_keyboard_scroll(cx, scroll);
2215 }
2216
2217 pub(crate) fn do_keyboard_scroll(&self, cx: &mut JSContext, scroll: KeyboardScroll) {
2218 let scroll_axis = match scroll {
2219 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2220 _ => ScrollingBoxAxis::Y,
2221 };
2222
2223 let document = self.window.Document();
2224 let mut scrolling_box = document
2225 .focus_handler()
2226 .focused_area()
2227 .element()
2228 .or(self.most_recently_clicked_element.get().as_deref())
2229 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2230 .unwrap_or_else(|| {
2231 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2232 });
2233
2234 while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2235 if scrolling_box.is_viewport() {
2237 break;
2238 }
2239 let parent = scrolling_box.parent().unwrap_or_else(|| {
2240 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2241 });
2242 scrolling_box = parent;
2243 }
2244
2245 let calculate_current_scroll_offset_and_delta = || {
2246 const LINE_HEIGHT: f32 = 76.0;
2247 const LINE_WIDTH: f32 = 76.0;
2248
2249 let current_scroll_offset = scrolling_box.scroll_position();
2250 (
2251 current_scroll_offset,
2252 match scroll {
2253 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2254 KeyboardScroll::End => Vector2D::new(
2255 0.0,
2256 -current_scroll_offset.y + scrolling_box.content_size().height -
2257 scrolling_box.size().height,
2258 ),
2259 KeyboardScroll::PageDown => {
2260 Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
2261 },
2262 KeyboardScroll::PageUp => {
2263 Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
2264 },
2265 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2266 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2267 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2268 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2269 },
2270 )
2271 };
2272
2273 let parent_pipeline = self.window.parent_info();
2277 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2278 let (_, delta) = calculate_current_scroll_offset_and_delta();
2279 self.window
2280 .paint_api()
2281 .scroll_viewport_by_delta(self.window.webview_id(), delta);
2282 }
2283
2284 if !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2287 assert!(scrolling_box.is_viewport());
2288
2289 let window_proxy = document.window().window_proxy();
2290 if let Some(iframe) = window_proxy.frame_element() {
2291 let iframe_window = iframe.owner_window();
2294 let mut realm = enter_auto_realm(cx, &*iframe_window);
2295 let cx = &mut realm;
2296 iframe_window
2297 .Document()
2298 .event_handler()
2299 .do_keyboard_scroll(cx, scroll);
2300 } else if let Some(parent_pipeline) = parent_pipeline {
2301 document.window().send_to_constellation(
2305 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2306 );
2307 };
2308 return;
2309 }
2310
2311 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2312 scrolling_box.scroll_to(cx, delta + current_scroll_offset, ScrollBehavior::Auto);
2313 }
2314
2315 fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2318 let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2319
2320 if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2321 return pointer_id;
2322 }
2323
2324 let pointer_id = self.next_touch_pointer_id.get();
2325 active_pointer_ids.insert(touch_id, pointer_id);
2326 self.next_touch_pointer_id.set(pointer_id + 1);
2327 pointer_id
2328 }
2329
2330 fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2332 self.active_pointer_ids.borrow_mut().remove(&touch_id);
2333 }
2334
2335 fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2338 self.active_pointer_ids
2341 .borrow()
2342 .values()
2343 .min()
2344 .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2345 }
2346
2347 #[allow(clippy::too_many_arguments)]
2351 fn fire_pointer_event_for_touch(
2352 &self,
2353 cx: &mut js::context::JSContext,
2354 target_element: &Element,
2355 touch: &Touch,
2356 pointer_id: i32,
2357 event_name: &str,
2358 is_primary: bool,
2359 pointer_type: &str,
2360 input_event: &ConstellationInputEvent,
2361 hit_test_result: &HitTestResult,
2362 ) {
2363 let mut targets: Vec<_> = target_element
2364 .upcast::<Node>()
2365 .inclusive_ancestors_in_flat_tree()
2366 .collect();
2367
2368 if event_name == "pointerenter" {
2370 targets.reverse();
2371 }
2372
2373 for target in targets {
2374 let pointer_event = touch.to_pointer_event(
2375 cx,
2376 &self.window,
2377 event_name,
2378 pointer_id,
2379 is_primary,
2380 pointer_type,
2381 input_event.active_keyboard_modifiers,
2382 false,
2383 Some(hit_test_result.point_in_node),
2384 );
2385 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2386 }
2387 }
2388
2389 pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2390 self.access_key_handlers
2391 .borrow()
2392 .values()
2393 .any(|value| &**value == element)
2394 }
2395
2396 pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2397 self.access_key_handlers
2398 .borrow_mut()
2399 .retain(|_, value| &**value != element)
2400 }
2401
2402 pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2403 let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2404 access_key_handlers
2406 .entry(code.into())
2407 .or_insert(Dom::from_ref(element));
2408 }
2409
2410 fn maybe_handle_accesskey(
2411 &self,
2412 cx: &mut js::context::JSContext,
2413 event: &KeyboardEvent,
2414 ) -> bool {
2415 #[cfg(target_os = "macos")]
2416 let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2417 #[cfg(not(target_os = "macos"))]
2418 let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2419
2420 if event.modifiers() != access_key_modifiers {
2421 return false;
2422 }
2423
2424 let Ok(code) = Code::from_str(&event.Code().str()) else {
2425 return false;
2426 };
2427
2428 let Some(html_element) = self
2429 .access_key_handlers
2430 .borrow()
2431 .get(&code.into())
2432 .map(|html_element| html_element.as_rooted())
2433 else {
2434 return false;
2435 };
2436
2437 let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2445 return false;
2446 };
2447
2448 if command.disabled() || command.hidden() {
2449 return false;
2450 }
2451
2452 let node = html_element.upcast::<Node>();
2453 if !node.is_connected() {
2454 return false;
2455 }
2456
2457 for node in node.inclusive_ancestors_unrooted(cx.no_gc(), ShadowIncluding::Yes) {
2458 if node
2459 .downcast::<HTMLElement>()
2460 .is_some_and(|html_element| html_element.Hidden())
2461 {
2462 return false;
2463 }
2464 }
2465
2466 self.focus_and_scroll_to_element_for_key_event(cx, html_element.upcast());
2469 command.perform_action(cx);
2470 true
2471 }
2472
2473 pub(crate) fn focus_and_scroll_to_element_for_key_event(
2474 &self,
2475 cx: &mut JSContext,
2476 element: &Element,
2477 ) {
2478 element.upcast::<Node>().run_the_focusing_steps(cx, None);
2479 let scroll_axis = ScrollAxisState {
2480 position: ScrollLogicalPosition::Center,
2481 requirement: ScrollRequirement::IfNotVisible,
2482 };
2483 element.scroll_into_view_with_options(
2484 cx,
2485 ScrollBehavior::Auto,
2486 scroll_axis,
2487 scroll_axis,
2488 None,
2489 None,
2490 );
2491 }
2492
2493 pub(crate) fn is_active_pointer(&self, pointer_id: i32) -> bool {
2496 if pointer_id == PointerId::Mouse as i32 {
2497 self.mouse_buttons_down.get() > 0
2499 } else {
2500 self.active_pointer_ids
2502 .borrow()
2503 .values()
2504 .any(|&id| id == pointer_id)
2505 }
2506 }
2507
2508 pub(crate) fn set_pending_pointer_capture(&self, pointer_id: i32, element: &Element) {
2511 self.pending_pointer_capture
2512 .borrow_mut()
2513 .insert(pointer_id, Dom::from_ref(element));
2514 }
2515
2516 pub(crate) fn clear_pending_pointer_capture(&self, pointer_id: i32) {
2519 self.pending_pointer_capture
2520 .borrow_mut()
2521 .remove(&pointer_id);
2522 }
2523
2524 pub(crate) fn has_pointer_capture(&self, pointer_id: i32, element: &Element) -> bool {
2527 self.pending_pointer_capture
2528 .borrow()
2529 .get(&pointer_id)
2530 .is_some_and(|el| &**el == element)
2531 }
2532
2533 fn get_pointer_capture_target(&self, pointer_id: i32) -> Option<DomRoot<Element>> {
2538 self.pointer_capture_target
2539 .borrow()
2540 .get(&pointer_id)
2541 .map(|el| DomRoot::from_ref(&**el))
2542 .filter(|el| el.upcast::<Node>().is_connected())
2543 }
2544
2545 fn release_disconnected_pointer_capture(
2551 &self,
2552 cx: &mut JSContext,
2553 pointer_id: i32,
2554 pointer_type: &str,
2555 is_primary: bool,
2556 ) -> bool {
2557 let capture_target = self
2558 .pointer_capture_target
2559 .borrow()
2560 .get(&pointer_id)
2561 .map(|el| DomRoot::from_ref(&**el));
2562 if let Some(capture_element) = capture_target &&
2563 !capture_element.upcast::<Node>().is_connected()
2564 {
2565 let document = self.window.Document();
2567 self.fire_pointer_capture_event_at_target(
2568 cx,
2569 "lostpointercapture",
2570 pointer_id,
2571 pointer_type,
2572 is_primary,
2573 document.upcast::<EventTarget>(),
2574 );
2575 self.pending_pointer_capture
2577 .borrow_mut()
2578 .remove(&pointer_id);
2579 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2580 return true;
2581 }
2582 false
2583 }
2584
2585 fn fire_pointer_capture_event(
2588 &self,
2589 cx: &mut JSContext,
2590 event_type: &str,
2591 pointer_id: i32,
2592 pointer_type: &str,
2593 is_primary: bool,
2594 target: &Element,
2595 ) {
2596 self.fire_pointer_capture_event_at_target(
2597 cx,
2598 event_type,
2599 pointer_id,
2600 pointer_type,
2601 is_primary,
2602 target.upcast::<EventTarget>(),
2603 );
2604 }
2605
2606 fn fire_pointer_capture_event_at_target(
2608 &self,
2609 cx: &mut JSContext,
2610 event_type: &str,
2611 pointer_id: i32,
2612 pointer_type: &str,
2613 is_primary: bool,
2614 target: &EventTarget,
2615 ) {
2616 let pointer_event = PointerEvent::new(
2617 cx,
2618 &self.window,
2619 event_type.into(),
2620 EventBubbles::Bubbles,
2621 EventCancelable::NotCancelable,
2622 Some(&self.window),
2623 0,
2624 Point2D::new(0, 0),
2625 Point2D::new(0, 0),
2626 Point2D::new(0, 0),
2627 Modifiers::empty(),
2628 0,
2629 0,
2630 None,
2631 None,
2632 pointer_id,
2633 1,
2634 1,
2635 0.0,
2636 0.0,
2637 0,
2638 0,
2639 0,
2640 PI / 2.0,
2641 0.0,
2642 DOMString::from(pointer_type),
2643 is_primary,
2644 vec![],
2645 vec![],
2646 );
2647 pointer_event.upcast::<Event>().set_composed(true);
2648 pointer_event.upcast::<Event>().fire(cx, target);
2649 }
2650
2651 #[expect(clippy::too_many_arguments)]
2655 fn fire_pointer_boundary_event(
2656 &self,
2657 cx: &mut JSContext,
2658 event_type: &str,
2659 bubbles: EventBubbles,
2660 pointer_id: i32,
2661 pointer_type: &str,
2662 is_primary: bool,
2663 target: &Element,
2664 related_target: Option<&Element>,
2665 ) {
2666 let pointer_event = PointerEvent::new(
2667 cx,
2668 &self.window,
2669 event_type.into(),
2670 bubbles,
2671 EventCancelable::NotCancelable,
2672 Some(&self.window),
2673 0,
2674 Point2D::new(0, 0),
2675 Point2D::new(0, 0),
2676 Point2D::new(0, 0),
2677 Modifiers::empty(),
2678 -1, self.mouse_buttons_down.get() as u16,
2680 related_target.map(|el| el.upcast::<EventTarget>()),
2681 None,
2682 pointer_id,
2683 1,
2684 1,
2685 0.0,
2686 0.0,
2687 0,
2688 0,
2689 0,
2690 PI / 2.0,
2691 0.0,
2692 DOMString::from(pointer_type),
2693 is_primary,
2694 vec![],
2695 vec![],
2696 );
2697 pointer_event.upcast::<Event>().set_composed(true);
2698 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2699 }
2700
2701 fn fire_pointer_capture_boundary_transition(
2706 &self,
2707 cx: &mut JSContext,
2708 pointer_id: i32,
2709 pointer_type: &str,
2710 is_primary: bool,
2711 old_target: &Element,
2712 new_target: &Element,
2713 ) {
2714 if pointer_type != "mouse" {
2715 return;
2716 }
2717 if old_target == new_target {
2718 return;
2719 }
2720 self.fire_pointer_boundary_event(
2721 cx,
2722 "pointerout",
2723 EventBubbles::Bubbles,
2724 pointer_id,
2725 pointer_type,
2726 is_primary,
2727 old_target,
2728 Some(new_target),
2729 );
2730 self.fire_pointer_boundary_event(
2731 cx,
2732 "pointerleave",
2733 EventBubbles::DoesNotBubble,
2734 pointer_id,
2735 pointer_type,
2736 is_primary,
2737 old_target,
2738 Some(new_target),
2739 );
2740 self.fire_pointer_boundary_event(
2741 cx,
2742 "pointerover",
2743 EventBubbles::Bubbles,
2744 pointer_id,
2745 pointer_type,
2746 is_primary,
2747 new_target,
2748 Some(old_target),
2749 );
2750 self.fire_pointer_boundary_event(
2751 cx,
2752 "pointerenter",
2753 EventBubbles::DoesNotBubble,
2754 pointer_id,
2755 pointer_type,
2756 is_primary,
2757 new_target,
2758 Some(old_target),
2759 );
2760 }
2761
2762 fn implicit_release_pointer_capture(
2765 &self,
2766 cx: &mut JSContext,
2767 pointer_id: i32,
2768 pointer_type: &str,
2769 is_primary: bool,
2770 ) {
2771 let capture_element = self
2772 .pointer_capture_target
2773 .borrow()
2774 .get(&pointer_id)
2775 .map(|el| DomRoot::from_ref(&**el));
2776 if let Some(capture_element) = capture_element {
2777 if capture_element.upcast::<Node>().is_connected() {
2778 self.fire_pointer_capture_event(
2779 cx,
2780 "lostpointercapture",
2781 pointer_id,
2782 pointer_type,
2783 is_primary,
2784 &capture_element,
2785 );
2786 if let Some(hover_target) = self.current_hover_target.get() {
2789 self.fire_pointer_capture_boundary_transition(
2790 cx,
2791 pointer_id,
2792 pointer_type,
2793 is_primary,
2794 &capture_element,
2795 &hover_target,
2796 );
2797 }
2798 } else {
2799 let document = self.window.Document();
2800 self.fire_pointer_capture_event_at_target(
2801 cx,
2802 "lostpointercapture",
2803 pointer_id,
2804 pointer_type,
2805 is_primary,
2806 document.upcast::<EventTarget>(),
2807 );
2808 }
2809 }
2810 self.pending_pointer_capture
2811 .borrow_mut()
2812 .remove(&pointer_id);
2813 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2814 }
2815
2816 fn process_pending_pointer_capture(
2820 &self,
2821 cx: &mut JSContext,
2822 pointer_id: i32,
2823 pointer_type: &str,
2824 is_primary: bool,
2825 ) {
2826 let pending = self
2827 .pending_pointer_capture
2828 .borrow()
2829 .get(&pointer_id)
2830 .map(|el| DomRoot::from_ref(&**el));
2831 let current = self
2832 .pointer_capture_target
2833 .borrow()
2834 .get(&pointer_id)
2835 .map(|el| DomRoot::from_ref(&**el));
2836
2837 let pending_connected = pending
2840 .as_ref()
2841 .is_some_and(|el| el.upcast::<Node>().is_connected());
2842
2843 let pointer_is_active = self.is_active_pointer(pointer_id);
2846
2847 match (&pending, ¤t) {
2848 (Some(pending_el), None) if pending_connected => {
2849 if pointer_is_active {
2850 if let Some(hover_target) = self.current_hover_target.get() {
2853 self.fire_pointer_capture_boundary_transition(
2854 cx,
2855 pointer_id,
2856 pointer_type,
2857 is_primary,
2858 &hover_target,
2859 pending_el,
2860 );
2861 }
2862 self.fire_pointer_capture_event(
2863 cx,
2864 "gotpointercapture",
2865 pointer_id,
2866 pointer_type,
2867 is_primary,
2868 pending_el,
2869 );
2870 self.pointer_capture_target
2871 .borrow_mut()
2872 .insert(pointer_id, Dom::from_ref(pending_el));
2873 } else {
2874 self.pending_pointer_capture
2875 .borrow_mut()
2876 .remove(&pointer_id);
2877 }
2878 },
2879 (Some(pending_el), Some(current_el))
2880 if pending_connected && pending_el != current_el =>
2881 {
2882 self.fire_pointer_capture_event(
2883 cx,
2884 "lostpointercapture",
2885 pointer_id,
2886 pointer_type,
2887 is_primary,
2888 current_el,
2889 );
2890 if pointer_is_active {
2891 self.fire_pointer_capture_boundary_transition(
2892 cx,
2893 pointer_id,
2894 pointer_type,
2895 is_primary,
2896 current_el,
2897 pending_el,
2898 );
2899 self.fire_pointer_capture_event(
2900 cx,
2901 "gotpointercapture",
2902 pointer_id,
2903 pointer_type,
2904 is_primary,
2905 pending_el,
2906 );
2907 self.pointer_capture_target
2908 .borrow_mut()
2909 .insert(pointer_id, Dom::from_ref(pending_el));
2910 } else {
2911 self.pending_pointer_capture
2912 .borrow_mut()
2913 .remove(&pointer_id);
2914 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2915 }
2916 },
2917 (None, Some(current_el)) | (Some(_), Some(current_el)) if !pending_connected => {
2918 self.fire_pointer_capture_event(
2919 cx,
2920 "lostpointercapture",
2921 pointer_id,
2922 pointer_type,
2923 is_primary,
2924 current_el,
2925 );
2926 if let Some(hover_target) = self.current_hover_target.get() {
2929 self.fire_pointer_capture_boundary_transition(
2930 cx,
2931 pointer_id,
2932 pointer_type,
2933 is_primary,
2934 current_el,
2935 &hover_target,
2936 );
2937 }
2938 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2939 if !pending_connected {
2940 self.pending_pointer_capture
2941 .borrow_mut()
2942 .remove(&pointer_id);
2943 }
2944 },
2945 _ => {},
2946 }
2947 }
2948}
2949
2950pub(crate) fn character_to_code(character: char) -> Option<Code> {
2951 Some(match character.to_ascii_lowercase() {
2952 '`' => Code::Backquote,
2953 '\\' => Code::Backslash,
2954 '[' | '{' => Code::BracketLeft,
2955 ']' | '}' => Code::BracketRight,
2956 ',' | '<' => Code::Comma,
2957 '0' => Code::Digit0,
2958 '1' => Code::Digit1,
2959 '2' => Code::Digit2,
2960 '3' => Code::Digit3,
2961 '4' => Code::Digit4,
2962 '5' => Code::Digit5,
2963 '6' => Code::Digit6,
2964 '7' => Code::Digit7,
2965 '8' => Code::Digit8,
2966 '9' => Code::Digit9,
2967 '=' => Code::Equal,
2968 'a' => Code::KeyA,
2969 'b' => Code::KeyB,
2970 'c' => Code::KeyC,
2971 'd' => Code::KeyD,
2972 'e' => Code::KeyE,
2973 'f' => Code::KeyF,
2974 'g' => Code::KeyG,
2975 'h' => Code::KeyH,
2976 'i' => Code::KeyI,
2977 'j' => Code::KeyJ,
2978 'k' => Code::KeyK,
2979 'l' => Code::KeyL,
2980 'm' => Code::KeyM,
2981 'n' => Code::KeyN,
2982 'o' => Code::KeyO,
2983 'p' => Code::KeyP,
2984 'q' => Code::KeyQ,
2985 'r' => Code::KeyR,
2986 's' => Code::KeyS,
2987 't' => Code::KeyT,
2988 'u' => Code::KeyU,
2989 'v' => Code::KeyV,
2990 'w' => Code::KeyW,
2991 'x' => Code::KeyX,
2992 'y' => Code::KeyY,
2993 'z' => Code::KeyZ,
2994 '-' => Code::Minus,
2995 '.' => Code::Period,
2996 '\'' | '"' => Code::Quote,
2997 ';' => Code::Semicolon,
2998 '/' => Code::Slash,
2999 ' ' => Code::Space,
3000 _ => return None,
3001 })
3002}
3003
3004impl Element {
3005 fn inclusive_ancestor_element_in_non_ua_shadow_root(&self) -> DomRoot<Element> {
3007 if !self.upcast::<Node>().is_in_ua_widget() {
3008 return DomRoot::from_ref(self);
3009 }
3010 let Some(shadow_root) = self.containing_shadow_root() else {
3011 return DomRoot::from_ref(self);
3012 };
3013 shadow_root
3014 .Host()
3015 .inclusive_ancestor_element_in_non_ua_shadow_root()
3016 }
3017}