1use std::array::from_ref;
6use std::cell::{Cell, RefCell};
7use std::f64::consts::PI;
8use std::mem;
9use std::rc::Rc;
10use std::str::FromStr;
11use std::time::{Duration, Instant};
12
13use embedder_traits::{
14 Cursor, EditingActionEvent, EmbedderMsg, ImeEvent, InputEvent, InputEventId, InputEventOutcome,
15 InputEventResult, KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction,
16 MouseButtonEvent, MouseLeftViewportEvent, TouchEvent as EmbedderTouchEvent, TouchEventType,
17 TouchId, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
18};
19#[cfg(feature = "gamepad")]
20use embedder_traits::{
21 GamepadEvent as EmbedderGamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType,
22};
23use euclid::{Point2D, Vector2D};
24use js::context::JSContext;
25use js::jsapi::JSAutoRealm;
26use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
27use layout_api::{ScrollContainerQueryFlags, node_id_from_scroll_id};
28use rustc_hash::FxHashMap;
29use script_bindings::cell::DomRefCell;
30use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
31use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
32use script_bindings::codegen::GenericBindings::EventBinding::EventMethods;
33use script_bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElementMethods;
34use script_bindings::codegen::GenericBindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
35use script_bindings::codegen::GenericBindings::KeyboardEventBinding::KeyboardEventMethods;
36use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
37use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
38use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
39use script_bindings::inheritance::Castable;
40use script_bindings::match_domstring_ascii;
41use script_bindings::num::Finite;
42use script_bindings::reflector::DomObject;
43use script_bindings::root::{Dom, DomRoot, DomSlice};
44use script_bindings::script_runtime::CanGc;
45use script_bindings::str::DOMString;
46use script_traits::ConstellationInputEvent;
47use servo_base::generic_channel::GenericCallback;
48use servo_config::pref;
49use servo_constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
50use style::Atom;
51use style_traits::CSSPixel;
52use webrender_api::ExternalScrollId;
53
54use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
55use crate::dom::bindings::refcounted::Trusted;
56use crate::dom::bindings::root::MutNullableDom;
57use crate::dom::bindings::trace::NoTrace;
58use crate::dom::clipboardevent::ClipboardEventType;
59use crate::dom::document::FireMouseEventType;
60use crate::dom::document::focus::FocusableArea;
61use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
62#[cfg(feature = "gamepad")]
63use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
64#[cfg(feature = "gamepad")]
65use crate::dom::gamepad::gamepadevent::GamepadEventType;
66use crate::dom::inputevent::HitTestResult;
67use crate::dom::interactive_element_command::InteractiveElementCommand;
68use crate::dom::keyboardevent::KeyboardEvent;
69use crate::dom::node::{self, Node, NodeTraits, ShadowIncluding};
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_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}
203
204impl DocumentEventHandler {
205 pub(crate) fn new(window: &Window) -> Self {
206 Self {
207 window: Dom::from_ref(window),
208 pending_input_events: Default::default(),
209 mouse_move_event_index: Default::default(),
210 coalesced_mouse_move_event_ids: Default::default(),
211 wheel_event_index: Default::default(),
212 coalesced_wheel_event_ids: Default::default(),
213 click_counting_info: Default::default(),
214 last_mouse_button_down_point: Default::default(),
215 mouse_buttons_down: Cell::new(0),
216 current_hover_target: Default::default(),
217 current_active_element: Default::default(),
218 most_recently_clicked_element: Default::default(),
219 most_recent_mousemove_point: Default::default(),
220 current_cursor: Default::default(),
221 active_touch_points: Default::default(),
222 active_keyboard_modifiers: Default::default(),
223 active_pointer_ids: Default::default(),
224 next_touch_pointer_id: Cell::new(1),
225 access_key_handlers: Default::default(),
226 }
227 }
228
229 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
231 let mut pending_input_events = self.pending_input_events.borrow_mut();
232 if matches!(event.event.event, InputEvent::MouseMove(..)) {
233 if let Some(mouse_move_event) = self
235 .mouse_move_event_index
236 .borrow()
237 .and_then(|index| pending_input_events.get_mut(index))
238 {
239 self.coalesced_mouse_move_event_ids
240 .borrow_mut()
241 .push(mouse_move_event.event.id);
242 *mouse_move_event = event;
243 return;
244 }
245
246 *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
247 }
248
249 if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
250 if let Some(existing_constellation_wheel_event) = self
252 .wheel_event_index
253 .borrow()
254 .and_then(|index| pending_input_events.get_mut(index)) &&
255 let InputEvent::Wheel(ref mut existing_wheel_event) =
256 existing_constellation_wheel_event.event.event &&
257 existing_wheel_event.delta.mode == new_wheel_event.delta.mode
258 {
259 self.coalesced_wheel_event_ids
260 .borrow_mut()
261 .push(existing_constellation_wheel_event.event.id);
262 existing_wheel_event.delta.x += new_wheel_event.delta.x;
263 existing_wheel_event.delta.y += new_wheel_event.delta.y;
264 existing_wheel_event.delta.z += new_wheel_event.delta.z;
265 existing_wheel_event.point = new_wheel_event.point;
266 existing_constellation_wheel_event.event.id = event.event.id;
267 return;
268 }
269
270 *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
271 }
272
273 pending_input_events.push(event);
274 }
275
276 pub(crate) fn has_pending_input_events(&self) -> bool {
279 !self.pending_input_events.borrow().is_empty()
280 }
281
282 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
283 #[cfg(target_os = "macos")]
284 {
285 self.active_keyboard_modifiers
286 .get()
287 .contains(Modifiers::META)
288 }
289 #[cfg(not(target_os = "macos"))]
290 {
291 self.active_keyboard_modifiers
292 .get()
293 .contains(Modifiers::CONTROL)
294 }
295 }
296
297 pub(crate) fn handle_pending_input_events(&self, cx: &mut JSContext) {
298 debug_assert!(
299 !self.pending_input_events.borrow().is_empty(),
300 "handle_pending_input_events called with no events"
301 );
302 let _realm = enter_realm(&*self.window);
303
304 *self.mouse_move_event_index.borrow_mut() = None;
306 *self.wheel_event_index.borrow_mut() = None;
307 let pending_input_events = mem::take(&mut *self.pending_input_events.borrow_mut());
308 let mut coalesced_mouse_move_event_ids =
309 mem::take(&mut *self.coalesced_mouse_move_event_ids.borrow_mut());
310 let mut coalesced_wheel_event_ids =
311 mem::take(&mut *self.coalesced_wheel_event_ids.borrow_mut());
312
313 let mut input_event_outcomes = Vec::with_capacity(
314 pending_input_events.len() +
315 coalesced_mouse_move_event_ids.len() +
316 coalesced_wheel_event_ids.len(),
317 );
318 for event in pending_input_events {
324 self.active_keyboard_modifiers
325 .set(event.active_keyboard_modifiers);
326 let result = match event.event.event {
327 InputEvent::MouseButton(mouse_button_event) => {
328 self.handle_native_mouse_button_event(cx, mouse_button_event, &event);
329 InputEventResult::default()
330 },
331 InputEvent::MouseMove(_) => {
332 self.handle_native_mouse_move_event(cx, &event);
333 input_event_outcomes.extend(
334 mem::take(&mut coalesced_mouse_move_event_ids)
335 .into_iter()
336 .map(|id| InputEventOutcome {
337 id,
338 result: InputEventResult::default(),
339 }),
340 );
341 InputEventResult::default()
342 },
343 InputEvent::MouseLeftViewport(mouse_leave_event) => {
344 self.handle_mouse_left_viewport_event(cx, &event, &mouse_leave_event);
345 InputEventResult::default()
346 },
347 InputEvent::Touch(touch_event) => self.handle_touch_event(cx, touch_event, &event),
348 InputEvent::Wheel(wheel_event) => {
349 let result = self.handle_wheel_event(cx, wheel_event, &event);
350 input_event_outcomes.extend(
351 mem::take(&mut coalesced_wheel_event_ids)
352 .into_iter()
353 .map(|id| InputEventOutcome { id, result }),
354 );
355 result
356 },
357 InputEvent::Keyboard(keyboard_event) => {
358 self.handle_keyboard_event(cx, keyboard_event)
359 },
360 InputEvent::Ime(ime_event) => self.handle_ime_event(cx, ime_event),
361 #[cfg(feature = "gamepad")]
362 InputEvent::Gamepad(gamepad_event) => {
363 self.handle_gamepad_event(gamepad_event);
364 InputEventResult::default()
365 },
366 InputEvent::EditingAction(editing_action_event) => {
367 self.handle_editing_action(cx, None, editing_action_event)
368 },
369 };
370
371 input_event_outcomes.push(InputEventOutcome {
372 id: event.event.id,
373 result,
374 });
375 }
376
377 self.notify_embedder_that_events_were_handled(input_event_outcomes);
378 }
379
380 fn notify_embedder_that_events_were_handled(
381 &self,
382 input_event_outcomes: Vec<InputEventOutcome>,
383 ) {
384 let trusted_window = Trusted::new(&*self.window);
387 self.window
388 .as_global_scope()
389 .task_manager()
390 .dom_manipulation_task_source()
391 .queue(task!(notify_webdriver_input_event_completed: move || {
392 let window = trusted_window.root();
393 window.send_to_embedder(
394 EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
395 }));
396 }
397
398 fn target_for_events_following_focus(&self) -> DomRoot<EventTarget> {
402 let document = self.window.Document();
403 match &*document.focus_handler().focused_area() {
404 FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
405 FocusableArea::IFrameViewport { iframe_element, .. } => {
406 DomRoot::from_ref(iframe_element.upcast())
407 },
408 FocusableArea::Viewport => document
409 .GetBody()
410 .map(DomRoot::upcast)
411 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast())),
412 }
413 }
414
415 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
416 if cursor == self.current_cursor.get() {
417 return;
418 }
419 self.current_cursor.set(cursor);
420 self.window.send_to_embedder(EmbedderMsg::SetCursor(
421 self.window.webview_id(),
422 cursor.unwrap_or_default(),
423 ));
424 }
425
426 fn handle_mouse_left_viewport_event(
427 &self,
428 cx: &mut JSContext,
429 input_event: &ConstellationInputEvent,
430 mouse_leave_event: &MouseLeftViewportEvent,
431 ) {
432 if let Some(current_hover_target) = self.current_hover_target.get() {
433 let current_hover_target = current_hover_target.upcast::<Node>();
434 for element in current_hover_target
435 .inclusive_ancestors(ShadowIncluding::Yes)
436 .filter_map(DomRoot::downcast::<Element>)
437 {
438 element.set_hover_state(false);
439 }
440
441 if let Some(hit_test_result) = self
442 .most_recent_mousemove_point
443 .get()
444 .and_then(|point| self.window.hit_test_from_point_in_viewport(point))
445 {
446 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
447 cx,
448 &self.window,
449 FireMouseEventType::Out,
450 &hit_test_result,
451 input_event,
452 );
453
454 mouse_out_event
456 .to_pointer_hover_event("pointerout", CanGc::from_cx(cx))
457 .upcast::<Event>()
458 .fire(current_hover_target.upcast(), CanGc::from_cx(cx));
459
460 mouse_out_event
461 .upcast::<Event>()
462 .fire(current_hover_target.upcast(), CanGc::from_cx(cx));
463
464 self.handle_mouse_enter_leave_event(
465 cx,
466 DomRoot::from_ref(current_hover_target),
467 None,
468 FireMouseEventType::Leave,
469 &hit_test_result,
470 input_event,
471 );
472 }
473 }
474
475 if !mouse_leave_event.focus_moving_to_another_iframe {
480 self.window
484 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
485 self.set_cursor(None);
486 } else {
487 self.current_cursor.set(None);
488 }
489
490 self.current_hover_target.set(None);
491 self.most_recent_mousemove_point.set(None);
492 }
493
494 fn handle_mouse_enter_leave_event(
495 &self,
496 cx: &mut JSContext,
497 event_target: DomRoot<Node>,
498 related_target: Option<DomRoot<Node>>,
499 event_type: FireMouseEventType,
500 hit_test_result: &HitTestResult,
501 input_event: &ConstellationInputEvent,
502 ) {
503 assert!(matches!(
504 event_type,
505 FireMouseEventType::Enter | FireMouseEventType::Leave
506 ));
507
508 let common_ancestor = match related_target.as_ref() {
509 Some(related_target) => event_target
510 .common_ancestor_in_flat_tree(related_target)
511 .unwrap_or_else(|| DomRoot::from_ref(&*event_target)),
512 None => DomRoot::from_ref(&*event_target),
513 };
514
515 let mut targets = vec![];
518 let mut current = Some(event_target);
519 while let Some(node) = current {
520 if node == common_ancestor {
521 break;
522 }
523 current = node.parent_in_flat_tree();
524 targets.push(node);
525 }
526
527 if event_type == FireMouseEventType::Enter {
530 targets = targets.into_iter().rev().collect();
531 }
532
533 let pointer_event_name = match event_type {
534 FireMouseEventType::Enter => "pointerenter",
535 FireMouseEventType::Leave => "pointerleave",
536 _ => unreachable!(),
537 };
538
539 for target in targets {
540 let mouse_event = MouseEvent::new_for_platform_motion_event(
541 cx,
542 &self.window,
543 event_type,
544 hit_test_result,
545 input_event,
546 );
547 mouse_event
548 .upcast::<Event>()
549 .set_related_target(related_target.as_ref().map(|target| target.upcast()));
550
551 mouse_event
553 .to_pointer_hover_event(pointer_event_name, CanGc::from_cx(cx))
554 .upcast::<Event>()
555 .fire(target.upcast(), CanGc::from_cx(cx));
556
557 mouse_event
559 .upcast::<Event>()
560 .fire(target.upcast(), CanGc::from_cx(cx));
561 }
562 }
563
564 fn handle_native_mouse_move_event(
566 &self,
567 cx: &mut JSContext,
568 input_event: &ConstellationInputEvent,
569 ) {
570 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
572 return;
573 };
574
575 let old_mouse_move_point = self
576 .most_recent_mousemove_point
577 .replace(Some(hit_test_result.point_in_frame));
578 if old_mouse_move_point == Some(hit_test_result.point_in_frame) {
579 return;
580 }
581
582 self.set_cursor(Some(hit_test_result.cursor));
584
585 let Some(new_target) = hit_test_result
586 .node
587 .inclusive_ancestors(ShadowIncluding::Yes)
588 .find_map(DomRoot::downcast::<Element>)
589 else {
590 return;
591 };
592
593 let old_hover_target = self.current_hover_target.get();
594 let target_has_changed = old_hover_target
595 .as_ref()
596 .is_none_or(|old_target| *old_target != new_target);
597
598 if target_has_changed {
601 if let Some(old_target) = self.current_hover_target.get() {
603 let old_target_is_ancestor_of_new_target = old_target
604 .upcast::<Node>()
605 .is_ancestor_of(new_target.upcast::<Node>());
606
607 if !old_target_is_ancestor_of_new_target {
610 for element in old_target
611 .upcast::<Node>()
612 .inclusive_ancestors(ShadowIncluding::Yes)
613 .filter_map(DomRoot::downcast::<Element>)
614 {
615 element.set_hover_state(false);
616 }
617 }
618
619 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
620 cx,
621 &self.window,
622 FireMouseEventType::Out,
623 &hit_test_result,
624 input_event,
625 );
626 mouse_out_event
627 .upcast::<Event>()
628 .set_related_target(Some(new_target.upcast()));
629
630 mouse_out_event
632 .to_pointer_hover_event("pointerout", CanGc::from_cx(cx))
633 .upcast::<Event>()
634 .fire(old_target.upcast(), CanGc::from_cx(cx));
635
636 mouse_out_event
637 .upcast::<Event>()
638 .fire(old_target.upcast(), CanGc::from_cx(cx));
639
640 if !old_target_is_ancestor_of_new_target {
641 let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
642 let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
643 self.handle_mouse_enter_leave_event(
644 cx,
645 event_target,
646 moving_into,
647 FireMouseEventType::Leave,
648 &hit_test_result,
649 input_event,
650 );
651 }
652 }
653
654 for element in new_target
656 .upcast::<Node>()
657 .inclusive_ancestors(ShadowIncluding::Yes)
658 .filter_map(DomRoot::downcast::<Element>)
659 {
660 element.set_hover_state(true);
661 }
662
663 let mouse_over_event = MouseEvent::new_for_platform_motion_event(
664 cx,
665 &self.window,
666 FireMouseEventType::Over,
667 &hit_test_result,
668 input_event,
669 );
670 mouse_over_event
671 .upcast::<Event>()
672 .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
673
674 mouse_over_event
676 .to_pointer_hover_event("pointerover", CanGc::from_cx(cx))
677 .upcast::<Event>()
678 .dispatch(cx, new_target.upcast(), false);
679
680 mouse_over_event
681 .upcast::<Event>()
682 .dispatch(cx, new_target.upcast(), false);
683
684 let moving_from =
685 old_hover_target.map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
686 let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
687 self.handle_mouse_enter_leave_event(
688 cx,
689 event_target,
690 moving_from,
691 FireMouseEventType::Enter,
692 &hit_test_result,
693 input_event,
694 );
695 }
696
697 let mouse_event = MouseEvent::new_for_platform_motion_event(
700 cx,
701 &self.window,
702 FireMouseEventType::Move,
703 &hit_test_result,
704 input_event,
705 );
706
707 let pointer_event =
709 mouse_event.to_pointer_event(Atom::from("pointermove"), CanGc::from_cx(cx));
710 pointer_event.upcast::<Event>().set_composed(true);
711 pointer_event
712 .upcast::<Event>()
713 .fire(new_target.upcast(), CanGc::from_cx(cx));
714
715 mouse_event
718 .upcast::<Event>()
719 .fire(new_target.upcast(), CanGc::from_cx(cx));
720
721 self.update_current_hover_target_and_status(Some(new_target));
722 }
723
724 fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
725 let current_hover_target = self.current_hover_target.get();
726 if current_hover_target == new_hover_target {
727 return;
728 }
729
730 let previous_hover_target = self.current_hover_target.get();
731 self.current_hover_target.set(new_hover_target.as_deref());
732
733 if let Some(target) = self.current_hover_target.get() &&
736 let Some(anchor) = target
737 .upcast::<Node>()
738 .inclusive_ancestors(ShadowIncluding::Yes)
739 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
740 {
741 let status = anchor
742 .full_href_url_for_user_interface()
743 .map(|url| url.to_string());
744 self.window
745 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
746 return;
747 }
748
749 if previous_hover_target.is_none_or(|previous_hover_target| {
754 previous_hover_target
755 .upcast::<Node>()
756 .inclusive_ancestors(ShadowIncluding::Yes)
757 .any(|node| node.is::<HTMLAnchorElement>())
758 }) {
759 self.window
760 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
761 }
762 }
763
764 pub(crate) fn handle_refresh_cursor(&self) {
765 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
766 return;
767 };
768
769 let Some(hit_test_result) = self
770 .window
771 .hit_test_from_point_in_viewport(most_recent_mousemove_point)
772 else {
773 return;
774 };
775
776 self.set_cursor(Some(hit_test_result.cursor));
777 }
778
779 fn set_active_element(&self, original_target: &Element) {
780 let find_element_for_activation = |element: &Element| {
781 let node: &Node = element.upcast();
782 if node.is_in_ua_widget() &&
783 let Some(containing_shadow_root) = node.containing_shadow_root()
784 {
785 return containing_shadow_root.Host();
786 }
787
788 if node.type_id() ==
790 NodeTypeId::Element(ElementTypeId::HTMLElement(
791 HTMLElementTypeId::HTMLLabelElement,
792 ))
793 {
794 let label = element.downcast::<HTMLLabelElement>().unwrap();
795 if let Some(control) = label.GetControl() {
796 return DomRoot::from_ref(control.upcast::<Element>());
797 }
798 }
799
800 DomRoot::from_ref(element)
801 };
802 let element_for_activation = find_element_for_activation(original_target);
803
804 if let Some(currently_active_element) = self.current_active_element.get() {
807 if currently_active_element == element_for_activation {
808 return;
809 }
810 self.unset_active_element();
811 }
812
813 element_for_activation.set_active_state(true);
814 self.current_active_element
815 .set(Some(&*element_for_activation));
816 }
817
818 fn unset_active_element(&self) {
819 if let Some(active_element) = self.current_active_element.take() {
820 active_element.set_active_state(false);
821 }
822 }
823
824 fn handle_native_mouse_button_event(
827 &self,
828 cx: &mut JSContext,
829 event: MouseButtonEvent,
830 input_event: &ConstellationInputEvent,
831 ) {
832 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
834 return;
835 };
836
837 debug!(
838 "{:?}: at {:?}",
839 event.action, hit_test_result.point_in_frame
840 );
841
842 let document = self.window.Document();
845 if event.action == MouseButtonAction::Down {
846 document
847 .focus_handler()
848 .set_sequential_focus_navigation_starting_point(&hit_test_result.node);
849 }
850
851 let Some(element) = hit_test_result
852 .node
853 .inclusive_ancestors(ShadowIncluding::Yes)
854 .find_map(DomRoot::downcast::<Element>)
855 else {
856 return;
857 };
858
859 let node = element.upcast::<Node>();
860 debug!("{:?} on {:?}", event.action, node.debug_str());
861
862 if event.button == MouseButton::Left {
867 if event.action == MouseButtonAction::Down {
868 self.set_active_element(&element);
869 }
870 if event.action == MouseButtonAction::Up {
871 self.unset_active_element();
872 }
873 }
874
875 if element.is_actually_disabled() {
879 return;
880 }
881
882 let mouse_event_type = match event.action {
883 embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
884 embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
885 };
886
887 if event.action == MouseButtonAction::Down {
894 self.click_counting_info
895 .borrow_mut()
896 .reset_click_count_if_necessary(event.button, hit_test_result.point_in_frame);
897 }
898
899 let mouse_event = MouseEvent::for_platform_button_event(
900 cx,
901 mouse_event_type,
902 event,
903 input_event.pressed_mouse_buttons,
904 &self.window,
905 &hit_test_result,
906 input_event.active_keyboard_modifiers,
907 self.click_counting_info.borrow().count + 1,
908 );
909
910 match event.action {
911 MouseButtonAction::Down => {
912 self.last_mouse_button_down_point
913 .set(Some(hit_test_result.point_in_frame));
914
915 let mouse_buttons_down = self.mouse_buttons_down.get();
917 let pointer_event_name = if mouse_buttons_down == 0 {
918 "pointerdown".into()
923 } else {
924 "pointermove".into()
930 };
931 mouse_event
932 .to_pointer_event(pointer_event_name, CanGc::from_cx(cx))
933 .upcast::<Event>()
934 .fire(node.upcast(), CanGc::from_cx(cx));
935
936 self.mouse_buttons_down.set(mouse_buttons_down + 1);
937
938 let result = mouse_event
940 .upcast::<Event>()
941 .dispatch(cx, node.upcast(), false);
942
943 if result {
946 document
950 .focus_handler()
951 .focus(cx, node.find_click_focusable_area());
952 }
953
954 if let MouseButton::Right = event.button {
957 self.maybe_show_context_menu(cx, node.upcast(), &hit_test_result, input_event);
958 }
959 },
960 MouseButtonAction::Up => {
962 let mouse_buttons_down = self.mouse_buttons_down.get();
964 let pointer_event_name = if mouse_buttons_down == 1 {
965 "pointerup".into()
970 } else {
971 "pointermove".into()
977 };
978 mouse_event
979 .to_pointer_event(pointer_event_name, CanGc::from_cx(cx))
980 .upcast::<Event>()
981 .fire(node.upcast(), CanGc::from_cx(cx));
982 self.mouse_buttons_down
983 .set(mouse_buttons_down.saturating_sub(1));
984
985 mouse_event
987 .upcast::<Event>()
988 .dispatch(cx, node.upcast(), false);
989
990 self.click_counting_info
994 .borrow_mut()
995 .increment_click_count(event.button, hit_test_result.point_in_frame);
996
997 self.maybe_trigger_click_for_mouse_button_down_event(
998 cx,
999 event,
1000 input_event,
1001 &hit_test_result,
1002 &element,
1003 );
1004 },
1005 }
1006 }
1007
1008 fn maybe_trigger_click_for_mouse_button_down_event(
1011 &self,
1012 cx: &mut JSContext,
1013 event: MouseButtonEvent,
1014 input_event: &ConstellationInputEvent,
1015 hit_test_result: &HitTestResult,
1016 element: &Element,
1017 ) {
1018 if event.button != MouseButton::Left {
1019 return;
1020 }
1021
1022 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
1023 return;
1024 };
1025
1026 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
1027 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
1028 if distance > maximum_click_distance {
1029 return;
1030 }
1031
1032 let element = match hit_test_result.node.find_click_focusable_area() {
1039 FocusableArea::Node { node, .. } => DomRoot::downcast::<Element>(node),
1040 _ => None,
1041 }
1042 .unwrap_or_else(|| DomRoot::from_ref(element));
1043 self.most_recently_clicked_element.set(Some(&*element));
1044
1045 let click_count = self.click_counting_info.borrow().count;
1046 element.set_click_in_progress(true);
1047 MouseEvent::for_platform_button_event(
1048 cx,
1049 atom!("click"),
1050 event,
1051 input_event.pressed_mouse_buttons,
1052 &self.window,
1053 hit_test_result,
1054 input_event.active_keyboard_modifiers,
1055 click_count,
1056 )
1057 .upcast::<Event>()
1058 .dispatch(cx, element.upcast(), false);
1059 element.set_click_in_progress(false);
1060
1061 if click_count.is_multiple_of(2) {
1070 MouseEvent::for_platform_button_event(
1071 cx,
1072 Atom::from("dblclick"),
1073 event,
1074 input_event.pressed_mouse_buttons,
1075 &self.window,
1076 hit_test_result,
1077 input_event.active_keyboard_modifiers,
1078 2,
1079 )
1080 .upcast::<Event>()
1081 .dispatch(cx, element.upcast(), false);
1082 }
1083 }
1084
1085 fn maybe_show_context_menu(
1087 &self,
1088 cx: &mut js::context::JSContext,
1089 target: &EventTarget,
1090 hit_test_result: &HitTestResult,
1091 input_event: &ConstellationInputEvent,
1092 ) {
1093 let menu_event = PointerEvent::new(
1095 &self.window, "contextmenu".into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
1102 hit_test_result.point_in_frame.to_i32(),
1103 hit_test_result
1104 .point_relative_to_initial_containing_block
1105 .to_i32(),
1106 input_event.active_keyboard_modifiers,
1107 2i16, input_event.pressed_mouse_buttons,
1109 None, None, PointerId::Mouse as i32, 1, 1, 0.5, 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from("mouse"), true, vec![], vec![], CanGc::from_cx(cx),
1126 );
1127 menu_event.upcast::<Event>().set_composed(true);
1128
1129 let result = menu_event
1131 .upcast::<Event>()
1132 .fire(target, CanGc::from_cx(cx));
1133
1134 if result {
1136 self.window
1137 .Document()
1138 .embedder_controls()
1139 .show_context_menu(hit_test_result);
1140 };
1141 }
1142
1143 fn handle_touch_event(
1144 &self,
1145 cx: &mut JSContext,
1146 event: EmbedderTouchEvent,
1147 input_event: &ConstellationInputEvent,
1148 ) -> InputEventResult {
1149 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1151 self.update_active_touch_points_when_early_return(event);
1152 return Default::default();
1153 };
1154
1155 let TouchId(identifier) = event.touch_id;
1156
1157 let Some(element) = hit_test_result
1158 .node
1159 .inclusive_ancestors(ShadowIncluding::Yes)
1160 .find_map(DomRoot::downcast::<Element>)
1161 else {
1162 self.update_active_touch_points_when_early_return(event);
1163 return Default::default();
1164 };
1165
1166 let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1167 let window = &*self.window;
1168
1169 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1170 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1171 let page_x =
1172 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1173 let page_y =
1174 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1175
1176 let pointer_touch = Touch::new(
1178 window,
1179 identifier,
1180 ¤t_target,
1181 client_x,
1182 client_y, client_x,
1184 client_y,
1185 page_x,
1186 page_y,
1187 CanGc::from_cx(cx),
1188 );
1189
1190 let pointer_event_name = match event.event_type {
1192 TouchEventType::Down => "pointerdown",
1193 TouchEventType::Move => "pointermove",
1194 TouchEventType::Up => "pointerup",
1195 TouchEventType::Cancel => "pointercancel",
1196 };
1197
1198 let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1200 let is_primary = self.is_primary_pointer(pointer_id);
1201
1202 if matches!(event.event_type, TouchEventType::Down) {
1205 let pointer_over = pointer_touch.to_pointer_event(
1207 window,
1208 "pointerover",
1209 pointer_id,
1210 is_primary,
1211 input_event.active_keyboard_modifiers,
1212 true, Some(hit_test_result.point_in_node),
1214 CanGc::from_cx(cx),
1215 );
1216 pointer_over
1217 .upcast::<Event>()
1218 .fire(¤t_target, CanGc::from_cx(cx));
1219
1220 self.fire_pointer_event_for_touch(
1222 cx,
1223 &element,
1224 &pointer_touch,
1225 pointer_id,
1226 "pointerenter",
1227 is_primary,
1228 input_event,
1229 &hit_test_result,
1230 );
1231 }
1232
1233 let pointer_event = pointer_touch.to_pointer_event(
1234 window,
1235 pointer_event_name,
1236 pointer_id,
1237 is_primary,
1238 input_event.active_keyboard_modifiers,
1239 event.is_cancelable(),
1240 Some(hit_test_result.point_in_node),
1241 CanGc::from_cx(cx),
1242 );
1243 pointer_event
1244 .upcast::<Event>()
1245 .fire(¤t_target, CanGc::from_cx(cx));
1246
1247 if matches!(
1250 event.event_type,
1251 TouchEventType::Up | TouchEventType::Cancel
1252 ) {
1253 let pointer_out = pointer_touch.to_pointer_event(
1255 window,
1256 "pointerout",
1257 pointer_id,
1258 is_primary,
1259 input_event.active_keyboard_modifiers,
1260 true, Some(hit_test_result.point_in_node),
1262 CanGc::from_cx(cx),
1263 );
1264 pointer_out
1265 .upcast::<Event>()
1266 .fire(¤t_target, CanGc::from_cx(cx));
1267
1268 self.fire_pointer_event_for_touch(
1270 cx,
1271 &element,
1272 &pointer_touch,
1273 pointer_id,
1274 "pointerleave",
1275 is_primary,
1276 input_event,
1277 &hit_test_result,
1278 );
1279 }
1280
1281 let (touch_dispatch_target, changed_touch) = match event.event_type {
1282 TouchEventType::Down => {
1283 self.active_touch_points
1285 .borrow_mut()
1286 .push(Dom::from_ref(&*pointer_touch));
1287 self.set_active_element(&element);
1288 (current_target, pointer_touch)
1289 },
1290 _ => {
1291 let mut active_touch_points = self.active_touch_points.borrow_mut();
1297 let Some(index) = active_touch_points
1298 .iter()
1299 .position(|point| point.Identifier() == identifier)
1300 else {
1301 warn!("No active touch point for {:?}", event.event_type);
1302 return Default::default();
1303 };
1304 let original_target = active_touch_points[index].Target();
1306
1307 let touch_with_touchstart_target = Touch::new(
1308 window,
1309 identifier,
1310 &original_target,
1311 client_x,
1312 client_y,
1313 client_x,
1314 client_y,
1315 page_x,
1316 page_y,
1317 CanGc::from_cx(cx),
1318 );
1319
1320 match event.event_type {
1322 TouchEventType::Move => {
1323 active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1324 },
1325 TouchEventType::Up | TouchEventType::Cancel => {
1326 active_touch_points.swap_remove(index);
1327 self.remove_pointer_id_for_touch(identifier);
1328 self.unset_active_element();
1329 },
1330 TouchEventType::Down => unreachable!("Should have been handled above"),
1331 }
1332 (original_target, touch_with_touchstart_target)
1333 },
1334 };
1335
1336 rooted_vec!(let mut target_touches);
1337 target_touches.extend(
1338 self.active_touch_points
1339 .borrow()
1340 .iter()
1341 .filter(|touch| touch.Target() == touch_dispatch_target)
1342 .cloned(),
1343 );
1344
1345 let event_name = match event.event_type {
1346 TouchEventType::Down => "touchstart",
1347 TouchEventType::Move => "touchmove",
1348 TouchEventType::Up => "touchend",
1349 TouchEventType::Cancel => "touchcancel",
1350 };
1351
1352 let touch_event = TouchEvent::new(
1353 window,
1354 event_name.into(),
1355 EventBubbles::Bubbles,
1356 EventCancelable::from(event.is_cancelable()),
1357 EventComposed::Composed,
1358 Some(window),
1359 0i32,
1360 &TouchList::new(
1361 window,
1362 self.active_touch_points.borrow().r(),
1363 CanGc::from_cx(cx),
1364 ),
1365 &TouchList::new(window, from_ref(&&*changed_touch), CanGc::from_cx(cx)),
1366 &TouchList::new(window, target_touches.r(), CanGc::from_cx(cx)),
1367 false,
1369 false,
1370 false,
1371 false,
1372 CanGc::from_cx(cx),
1373 );
1374 let event = touch_event.upcast::<Event>();
1375 event.fire(&touch_dispatch_target, CanGc::from_cx(cx));
1376 event.flags().into()
1377 }
1378
1379 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1388 match event.event_type {
1389 TouchEventType::Down | TouchEventType::Move => {},
1390 TouchEventType::Up | TouchEventType::Cancel => {
1391 let mut active_touch_points = self.active_touch_points.borrow_mut();
1392 if let Some(index) = active_touch_points
1393 .iter()
1394 .position(|t| t.Identifier() == event.touch_id.0)
1395 {
1396 active_touch_points.swap_remove(index);
1397 self.remove_pointer_id_for_touch(event.touch_id.0);
1398 } else {
1399 warn!(
1400 "Received {:?} for a non-active touch point {}",
1401 event.event_type, event.touch_id.0
1402 );
1403 }
1404 },
1405 }
1406 }
1407
1408 fn handle_keyboard_event(
1410 &self,
1411 cx: &mut JSContext,
1412 keyboard_event: EmbedderKeyboardEvent,
1413 ) -> InputEventResult {
1414 let target = &self.target_for_events_following_focus();
1415 let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1416 cx,
1417 &self.window,
1418 keyboard_event.event.state.event_type().into(),
1419 &keyboard_event.event,
1420 );
1421
1422 let event = keyevent.upcast::<Event>();
1423
1424 event.set_composed(true);
1425
1426 event.fire(target, CanGc::from_cx(cx));
1427
1428 let mut flags = event.flags();
1429 if flags.contains(EventFlags::Canceled) {
1430 return flags.into();
1431 }
1432
1433 let is_character_value_key = matches!(
1439 keyboard_event.event.key,
1440 Key::Character(_) | Key::Named(NamedKey::Enter)
1441 );
1442 if keyboard_event.event.state == KeyState::Down &&
1443 is_character_value_key &&
1444 !keyboard_event.event.is_composing
1445 {
1446 let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1448 cx,
1449 &self.window,
1450 atom!("keypress"),
1451 &keyboard_event.event,
1452 );
1453 keypress_event.upcast::<Event>().set_composed(true);
1454 let event = keypress_event.upcast::<Event>();
1455 event.fire(target, CanGc::from_cx(cx));
1456 flags = event.flags();
1457 }
1458
1459 flags.into()
1460 }
1461
1462 fn handle_ime_event(&self, cx: &mut JSContext, event: ImeEvent) -> InputEventResult {
1463 let document = self.window.Document();
1464 let composition_event = match event {
1465 ImeEvent::Dismissed => {
1466 document.focus_handler().focus(cx, FocusableArea::Viewport);
1467 return Default::default();
1468 },
1469 ImeEvent::Composition(composition_event) => composition_event,
1470 };
1471
1472 let focused_area = document.focus_handler().focused_area();
1477 let Some(focused_element) = focused_area.element() else {
1478 return Default::default();
1480 };
1481
1482 let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1483 let event = CompositionEvent::new(
1484 &self.window,
1485 composition_event.state.event_type().into(),
1486 true,
1487 cancelable,
1488 Some(&self.window),
1489 0,
1490 DOMString::from(composition_event.data),
1491 CanGc::from_cx(cx),
1492 );
1493
1494 let event = event.upcast::<Event>();
1495 event.fire(focused_element.upcast(), CanGc::from_cx(cx));
1496 event.flags().into()
1497 }
1498
1499 fn handle_wheel_event(
1500 &self,
1501 cx: &mut JSContext,
1502 event: EmbedderWheelEvent,
1503 input_event: &ConstellationInputEvent,
1504 ) -> InputEventResult {
1505 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1507 return Default::default();
1508 };
1509
1510 let Some(el) = hit_test_result
1511 .node
1512 .inclusive_ancestors(ShadowIncluding::Yes)
1513 .find_map(DomRoot::downcast::<Element>)
1514 else {
1515 return Default::default();
1516 };
1517
1518 let node = el.upcast::<Node>();
1519 debug!(
1520 "wheel: on {:?} at {:?}",
1521 node.debug_str(),
1522 hit_test_result.point_in_frame
1523 );
1524
1525 let dom_event = WheelEvent::new(
1527 &self.window,
1528 "wheel".into(),
1529 EventBubbles::Bubbles,
1530 EventCancelable::Cancelable,
1531 Some(&self.window),
1532 0i32,
1533 hit_test_result.point_in_frame.to_i32(),
1534 hit_test_result.point_in_frame.to_i32(),
1535 hit_test_result
1536 .point_relative_to_initial_containing_block
1537 .to_i32(),
1538 input_event.active_keyboard_modifiers,
1539 0i16,
1540 input_event.pressed_mouse_buttons,
1541 None,
1542 None,
1543 Finite::wrap(-event.delta.x),
1548 Finite::wrap(-event.delta.y),
1549 Finite::wrap(-event.delta.z),
1550 event.delta.mode as u32,
1551 CanGc::from_cx(cx),
1552 );
1553
1554 let dom_event = dom_event.upcast::<Event>();
1555 dom_event.set_trusted(true);
1556 dom_event.set_composed(true);
1557 dom_event.fire(node.upcast(), CanGc::from_cx(cx));
1558
1559 dom_event.flags().into()
1560 }
1561
1562 #[cfg(feature = "gamepad")]
1563 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1564 match gamepad_event {
1565 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1566 self.handle_gamepad_connect(
1567 index.0,
1568 name,
1569 bounds.axis_bounds,
1570 bounds.button_bounds,
1571 supported_haptic_effects,
1572 );
1573 },
1574 EmbedderGamepadEvent::Disconnected(index) => {
1575 self.handle_gamepad_disconnect(index.0);
1576 },
1577 EmbedderGamepadEvent::Updated(index, update_type) => {
1578 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1579 },
1580 };
1581 }
1582
1583 #[cfg(feature = "gamepad")]
1585 fn handle_gamepad_connect(
1586 &self,
1587 _index: usize,
1591 name: String,
1592 axis_bounds: (f64, f64),
1593 button_bounds: (f64, f64),
1594 supported_haptic_effects: GamepadSupportedHapticEffects,
1595 ) {
1596 let trusted_window = Trusted::new(&*self.window);
1599 self.window
1600 .upcast::<GlobalScope>()
1601 .task_manager()
1602 .gamepad_task_source()
1603 .queue(task!(gamepad_connected: move |cx| {
1604 let window = trusted_window.root();
1605
1606 let navigator = window.Navigator();
1607 let selected_index = navigator.select_gamepad_index();
1608 let gamepad = Gamepad::new(
1609 &window,
1610 selected_index,
1611 name,
1612 "standard".into(),
1613 axis_bounds,
1614 button_bounds,
1615 supported_haptic_effects,
1616 false,
1617 CanGc::from_cx(cx),
1618 );
1619 navigator.set_gamepad(selected_index as usize, &gamepad, CanGc::from_cx(cx));
1620 }));
1621 }
1622
1623 #[cfg(feature = "gamepad")]
1625 fn handle_gamepad_disconnect(&self, index: usize) {
1626 let trusted_window = Trusted::new(&*self.window);
1627 self.window
1628 .upcast::<GlobalScope>()
1629 .task_manager()
1630 .gamepad_task_source()
1631 .queue(task!(gamepad_disconnected: move |cx| {
1632 let window = trusted_window.root();
1633 let navigator = window.Navigator();
1634 if let Some(gamepad) = navigator.get_gamepad(index)
1635 && window.Document().is_fully_active() {
1636 gamepad.update_connected(false, gamepad.exposed(), CanGc::from_cx(cx));
1637 navigator.remove_gamepad(index);
1638 }
1639 }));
1640 }
1641
1642 #[cfg(feature = "gamepad")]
1644 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1645 use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods;
1646 use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
1647
1648 let trusted_window = Trusted::new(&*self.window);
1649
1650 self.window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1652 task!(update_gamepad_state: move || {
1653 let window = trusted_window.root();
1654 let navigator = window.Navigator();
1655 if let Some(gamepad) = navigator.get_gamepad(index) {
1656 let current_time = window.Performance().Now();
1657 gamepad.update_timestamp(*current_time);
1658 match update_type {
1659 GamepadUpdateType::Axis(index, value) => {
1660 gamepad.map_and_normalize_axes(index, value);
1661 },
1662 GamepadUpdateType::Button(index, value) => {
1663 gamepad.map_and_normalize_buttons(index, value);
1664 }
1665 };
1666 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1667 navigator.set_has_gamepad_gesture(true);
1668 navigator.GetGamepads()
1669 .iter()
1670 .filter_map(|g| g.as_ref())
1671 .for_each(|gamepad| {
1672 gamepad.set_exposed(true);
1673 gamepad.update_timestamp(*current_time);
1674 let new_gamepad = Trusted::new(&**gamepad);
1675 if window.Document().is_fully_active() {
1676 window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1677 task!(update_gamepad_connect: move |cx| {
1678 let gamepad = new_gamepad.root();
1679 gamepad.notify_event(GamepadEventType::Connected, CanGc::from_cx(cx));
1680 })
1681 );
1682 }
1683 });
1684 }
1685 }
1686 })
1687 );
1688 }
1689
1690 pub(crate) fn handle_editing_action(
1692 &self,
1693 cx: &mut JSContext,
1694 element: Option<DomRoot<Element>>,
1695 action: EditingActionEvent,
1696 ) -> InputEventResult {
1697 let clipboard_event_type = match action {
1698 EditingActionEvent::Copy => ClipboardEventType::Copy,
1699 EditingActionEvent::Cut => ClipboardEventType::Cut,
1700 EditingActionEvent::Paste => ClipboardEventType::Paste,
1701 };
1702
1703 let script_triggered = false;
1705
1706 let script_may_access_clipboard = false;
1710
1711 if script_triggered && !script_may_access_clipboard {
1713 return InputEventResult::empty();
1714 }
1715
1716 let clipboard_event = self.fire_clipboard_event(cx, element.clone(), clipboard_event_type);
1718
1719 let event = clipboard_event.upcast::<Event>();
1722 if !event.IsTrusted() {
1723 return event.flags().into();
1724 }
1725
1726 if event.DefaultPrevented() {
1728 let event_type = event.Type();
1729 match_domstring_ascii!(event_type,
1730
1731 "copy" => {
1732 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1735 let drag_data_store =
1736 clipboard_data.data_store().expect("This shouldn't fail");
1737 self.write_content_to_the_clipboard(&drag_data_store);
1738 }
1739 },
1740 "cut" => {
1741 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1744 let drag_data_store =
1745 clipboard_data.data_store().expect("This shouldn't fail");
1746 self.write_content_to_the_clipboard(&drag_data_store);
1747 }
1748
1749 self.fire_clipboard_event(cx, element, ClipboardEventType::Change);
1751 },
1752 "paste" => (),
1756 _ => (),
1757 )
1758 }
1759
1760 event.flags().into()
1763 }
1764
1765 pub(crate) fn fire_clipboard_event(
1767 &self,
1768 cx: &mut JSContext,
1769 target: Option<DomRoot<Element>>,
1770 clipboard_event_type: ClipboardEventType,
1771 ) -> DomRoot<ClipboardEvent> {
1772 let clipboard_event = ClipboardEvent::new(
1773 &self.window,
1774 None,
1775 clipboard_event_type.as_str().into(),
1776 EventBubbles::Bubbles,
1777 EventCancelable::Cancelable,
1778 None,
1779 CanGc::from_cx(cx),
1780 );
1781
1782 let mut drag_data_store = DragDataStore::new();
1785
1786 let trusted = true;
1790
1791 let target = target
1793 .map(DomRoot::upcast)
1794 .unwrap_or_else(|| self.target_for_events_following_focus());
1795
1796 match clipboard_event_type {
1799 ClipboardEventType::Copy | ClipboardEventType::Cut => {
1800 drag_data_store.set_mode(Mode::ReadWrite);
1802 },
1803 ClipboardEventType::Paste => {
1804 let (callback, receiver) =
1805 GenericCallback::new_blocking().expect("Could not create callback");
1806 self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
1807 self.window.webview_id(),
1808 callback,
1809 ));
1810 let text_contents = receiver
1811 .recv()
1812 .map(Result::unwrap_or_default)
1813 .unwrap_or_default();
1814
1815 drag_data_store.set_mode(Mode::ReadOnly);
1817 if trusted {
1819 let data = DOMString::from(text_contents);
1823 let type_ = DOMString::from("text/plain");
1824 let _ = drag_data_store.add(Kind::Text { data, type_ });
1825
1826 }
1832 },
1833 ClipboardEventType::Change => (),
1834 }
1835
1836 let clipboard_event_data = DataTransfer::new(
1838 &self.window,
1839 Rc::new(RefCell::new(Some(drag_data_store))),
1840 CanGc::from_cx(cx),
1841 );
1842
1843 clipboard_event.set_clipboard_data(Some(&clipboard_event_data));
1845
1846 let event = clipboard_event.upcast::<Event>();
1848 event.set_trusted(trusted);
1849
1850 event.set_composed(true);
1852
1853 event.dispatch(cx, &target, false);
1855
1856 DomRoot::from(clipboard_event)
1857 }
1858
1859 fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
1861 if drag_data_store.list_len() > 0 {
1863 self.window
1865 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1866 for item in drag_data_store.iter_item_list() {
1868 match item {
1869 Kind::Text { data, .. } => {
1870 self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
1874 self.window.webview_id(),
1875 data.to_string(),
1876 ));
1877 },
1878 Kind::File { .. } => {
1879 },
1883 }
1884 }
1885 } else {
1886 if drag_data_store.clear_was_called {
1888 self.window
1890 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1891 }
1894 }
1895 }
1896
1897 #[expect(unsafe_code)]
1900 pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
1901 let document = self.window.Document();
1903 if scrolled_node.is_root() {
1904 document.handle_viewport_scroll_event();
1905 } else {
1906 let node_id = node_id_from_scroll_id(scrolled_node.0 as usize);
1910 let node = unsafe {
1911 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
1912 };
1913 let Some(element) = node
1914 .inclusive_ancestors(ShadowIncluding::Yes)
1915 .find_map(DomRoot::downcast::<Element>)
1916 else {
1917 return;
1918 };
1919
1920 element.handle_scroll_event();
1921 }
1922 }
1923
1924 pub(crate) fn maybe_dispatch_simulated_click(
1930 &self,
1931 cx: &mut JSContext,
1932 node: &Node,
1933 event: &KeyboardEvent,
1934 ) -> bool {
1935 if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
1936 {
1937 return false;
1938 }
1939
1940 if node
1944 .downcast::<Element>()
1945 .and_then(Element::as_maybe_activatable)
1946 .is_none()
1947 {
1948 return false;
1949 }
1950
1951 node.fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
1952 true
1953 }
1954
1955 pub(crate) fn run_default_keyboard_event_handler(
1956 &self,
1957 cx: &mut js::context::JSContext,
1958 node: &Node,
1959 event: &KeyboardEvent,
1960 ) {
1961 if event.upcast::<Event>().type_() != atom!("keydown") {
1962 return;
1963 }
1964
1965 if self.maybe_dispatch_simulated_click(cx, node, event) {
1966 return;
1967 }
1968
1969 if self.maybe_handle_accesskey(cx, event) {
1970 return;
1971 }
1972
1973 let mut is_space = false;
1974 let scroll = match event.key() {
1975 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
1976 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
1977 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
1978 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
1979 Key::Named(NamedKey::End) => KeyboardScroll::End,
1980 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
1981 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
1982 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
1983 Key::Character(string) if &string == " " => {
1984 is_space = true;
1985 if event.modifiers().contains(Modifiers::SHIFT) {
1986 KeyboardScroll::PageUp
1987 } else {
1988 KeyboardScroll::PageDown
1989 }
1990 },
1991 Key::Named(NamedKey::Tab) => {
1992 self.window
1998 .Document()
1999 .focus_handler()
2000 .sequential_focus_navigation_via_keyboard_event(cx, event);
2001 return;
2002 },
2003 _ => return,
2004 };
2005
2006 if !event.modifiers().is_empty() && !is_space {
2007 return;
2008 }
2009
2010 self.do_keyboard_scroll(scroll);
2011 }
2012
2013 pub(crate) fn do_keyboard_scroll(&self, scroll: KeyboardScroll) {
2014 let scroll_axis = match scroll {
2015 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2016 _ => ScrollingBoxAxis::Y,
2017 };
2018
2019 let document = self.window.Document();
2020 let mut scrolling_box = document
2021 .focus_handler()
2022 .focused_area()
2023 .element()
2024 .or(self.most_recently_clicked_element.get().as_deref())
2025 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2026 .unwrap_or_else(|| {
2027 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2028 });
2029
2030 while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2031 if scrolling_box.is_viewport() {
2033 break;
2034 }
2035 let parent = scrolling_box.parent().unwrap_or_else(|| {
2036 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2037 });
2038 scrolling_box = parent;
2039 }
2040
2041 let calculate_current_scroll_offset_and_delta = || {
2042 const LINE_HEIGHT: f32 = 76.0;
2043 const LINE_WIDTH: f32 = 76.0;
2044
2045 let current_scroll_offset = scrolling_box.scroll_position();
2046 (
2047 current_scroll_offset,
2048 match scroll {
2049 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2050 KeyboardScroll::End => Vector2D::new(
2051 0.0,
2052 -current_scroll_offset.y + scrolling_box.content_size().height -
2053 scrolling_box.size().height,
2054 ),
2055 KeyboardScroll::PageDown => {
2056 Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
2057 },
2058 KeyboardScroll::PageUp => {
2059 Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
2060 },
2061 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2062 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2063 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2064 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2065 },
2066 )
2067 };
2068
2069 let parent_pipeline = self.window.parent_info();
2073 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2074 let (_, delta) = calculate_current_scroll_offset_and_delta();
2075 self.window
2076 .paint_api()
2077 .scroll_viewport_by_delta(self.window.webview_id(), delta);
2078 }
2079
2080 if !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2083 assert!(scrolling_box.is_viewport());
2084
2085 let window_proxy = document.window().window_proxy();
2086 if let Some(iframe) = window_proxy.frame_element() {
2087 let cx = GlobalScope::get_cx();
2090 let iframe_window = iframe.owner_window();
2091 let _ac = JSAutoRealm::new(*cx, iframe_window.reflector().get_jsobject().get());
2092 iframe_window
2093 .Document()
2094 .event_handler()
2095 .do_keyboard_scroll(scroll);
2096 } else if let Some(parent_pipeline) = parent_pipeline {
2097 document.window().send_to_constellation(
2101 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2102 );
2103 };
2104 return;
2105 }
2106
2107 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2108 scrolling_box.scroll_to(delta + current_scroll_offset, ScrollBehavior::Auto);
2109 }
2110
2111 fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2114 let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2115
2116 if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2117 return pointer_id;
2118 }
2119
2120 let pointer_id = self.next_touch_pointer_id.get();
2121 active_pointer_ids.insert(touch_id, pointer_id);
2122 self.next_touch_pointer_id.set(pointer_id + 1);
2123 pointer_id
2124 }
2125
2126 fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2128 self.active_pointer_ids.borrow_mut().remove(&touch_id);
2129 }
2130
2131 fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2134 self.active_pointer_ids
2137 .borrow()
2138 .values()
2139 .min()
2140 .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2141 }
2142
2143 #[allow(clippy::too_many_arguments)]
2147 fn fire_pointer_event_for_touch(
2148 &self,
2149 cx: &mut js::context::JSContext,
2150 target_element: &Element,
2151 touch: &Touch,
2152 pointer_id: i32,
2153 event_name: &str,
2154 is_primary: bool,
2155 input_event: &ConstellationInputEvent,
2156 hit_test_result: &HitTestResult,
2157 ) {
2158 let mut targets: Vec<DomRoot<Node>> = vec![];
2160 let mut current: Option<DomRoot<Node>> = Some(DomRoot::from_ref(target_element.upcast()));
2161 while let Some(node) = current {
2162 targets.push(DomRoot::from_ref(&*node));
2163 current = node.parent_in_flat_tree();
2164 }
2165
2166 if event_name == "pointerenter" {
2168 targets.reverse();
2169 }
2170
2171 for target in targets {
2172 let pointer_event = touch.to_pointer_event(
2173 &self.window,
2174 event_name,
2175 pointer_id,
2176 is_primary,
2177 input_event.active_keyboard_modifiers,
2178 false,
2179 Some(hit_test_result.point_in_node),
2180 CanGc::from_cx(cx),
2181 );
2182 pointer_event
2183 .upcast::<Event>()
2184 .fire(target.upcast(), CanGc::from_cx(cx));
2185 }
2186 }
2187
2188 pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2189 self.access_key_handlers
2190 .borrow()
2191 .values()
2192 .any(|value| &**value == element)
2193 }
2194
2195 pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2196 self.access_key_handlers
2197 .borrow_mut()
2198 .retain(|_, value| &**value != element)
2199 }
2200
2201 pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2202 let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2203 access_key_handlers
2205 .entry(code.into())
2206 .or_insert(Dom::from_ref(element));
2207 }
2208
2209 fn maybe_handle_accesskey(
2210 &self,
2211 cx: &mut js::context::JSContext,
2212 event: &KeyboardEvent,
2213 ) -> bool {
2214 #[cfg(target_os = "macos")]
2215 let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2216 #[cfg(not(target_os = "macos"))]
2217 let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2218
2219 if event.modifiers() != access_key_modifiers {
2220 return false;
2221 }
2222
2223 let Ok(code) = Code::from_str(&event.Code().str()) else {
2224 return false;
2225 };
2226
2227 let Some(html_element) = self
2228 .access_key_handlers
2229 .borrow()
2230 .get(&code.into())
2231 .map(|html_element| html_element.as_rooted())
2232 else {
2233 return false;
2234 };
2235
2236 let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2244 return false;
2245 };
2246
2247 if command.disabled() || command.hidden() {
2248 return false;
2249 }
2250
2251 let node = html_element.upcast::<Node>();
2252 if !node.is_connected() {
2253 return false;
2254 }
2255
2256 for node in node.inclusive_ancestors(ShadowIncluding::Yes) {
2257 if node
2258 .downcast::<HTMLElement>()
2259 .is_some_and(|html_element| html_element.Hidden())
2260 {
2261 return false;
2262 }
2263 }
2264
2265 self.focus_and_scroll_to_element_for_key_event(cx, html_element.upcast());
2268 command.perform_action(cx);
2269 true
2270 }
2271
2272 pub(crate) fn focus_and_scroll_to_element_for_key_event(
2273 &self,
2274 cx: &mut JSContext,
2275 element: &Element,
2276 ) {
2277 element.upcast::<Node>().run_the_focusing_steps(cx, None);
2278 let scroll_axis = ScrollAxisState {
2279 position: ScrollLogicalPosition::Center,
2280 requirement: ScrollRequirement::IfNotVisible,
2281 };
2282 element.scroll_into_view_with_options(
2283 ScrollBehavior::Auto,
2284 scroll_axis,
2285 scroll_axis,
2286 None,
2287 None,
2288 );
2289 }
2290}
2291
2292pub(crate) fn character_to_code(character: char) -> Option<Code> {
2293 Some(match character.to_ascii_lowercase() {
2294 '`' => Code::Backquote,
2295 '\\' => Code::Backslash,
2296 '[' | '{' => Code::BracketLeft,
2297 ']' | '}' => Code::BracketRight,
2298 ',' | '<' => Code::Comma,
2299 '0' => Code::Digit0,
2300 '1' => Code::Digit1,
2301 '2' => Code::Digit2,
2302 '3' => Code::Digit3,
2303 '4' => Code::Digit4,
2304 '5' => Code::Digit5,
2305 '6' => Code::Digit6,
2306 '7' => Code::Digit7,
2307 '8' => Code::Digit8,
2308 '9' => Code::Digit9,
2309 '=' => Code::Equal,
2310 'a' => Code::KeyA,
2311 'b' => Code::KeyB,
2312 'c' => Code::KeyC,
2313 'd' => Code::KeyD,
2314 'e' => Code::KeyE,
2315 'f' => Code::KeyF,
2316 'g' => Code::KeyG,
2317 'h' => Code::KeyH,
2318 'i' => Code::KeyI,
2319 'j' => Code::KeyJ,
2320 'k' => Code::KeyK,
2321 'l' => Code::KeyL,
2322 'm' => Code::KeyM,
2323 'n' => Code::KeyN,
2324 'o' => Code::KeyO,
2325 'p' => Code::KeyP,
2326 'q' => Code::KeyQ,
2327 'r' => Code::KeyR,
2328 's' => Code::KeyS,
2329 't' => Code::KeyT,
2330 'u' => Code::KeyU,
2331 'v' => Code::KeyV,
2332 'w' => Code::KeyW,
2333 'x' => Code::KeyX,
2334 'y' => Code::KeyY,
2335 'z' => Code::KeyZ,
2336 '-' => Code::Minus,
2337 '.' => Code::Period,
2338 '\'' | '"' => Code::Quote,
2339 ';' => Code::Semicolon,
2340 '/' => Code::Slash,
2341 ' ' => Code::Space,
2342 _ => return None,
2343 })
2344}