1use std::array::from_ref;
6use std::cell::{Cell, RefCell};
7use std::cmp::Ordering;
8use std::f64::consts::PI;
9use std::mem;
10use std::rc::Rc;
11use std::str::FromStr;
12use std::time::{Duration, Instant};
13
14use embedder_traits::{
15 Cursor, EditingActionEvent, EmbedderMsg, ImeEvent, InputEvent, InputEventId, InputEventOutcome,
16 InputEventResult, KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction,
17 MouseButtonEvent, MouseLeftViewportEvent, TouchEvent as EmbedderTouchEvent, TouchEventType,
18 TouchId, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
19};
20#[cfg(feature = "gamepad")]
21use embedder_traits::{
22 GamepadEvent as EmbedderGamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType,
23};
24use euclid::{Point2D, Vector2D};
25use js::context::JSContext;
26use js::jsapi::JSAutoRealm;
27use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
28use layout_api::{ScrollContainerQueryFlags, node_id_from_scroll_id};
29use rustc_hash::FxHashMap;
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::NavigatorBinding::NavigatorMethods;
37use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
38use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
39use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
40use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
41use script_bindings::inheritance::Castable;
42use script_bindings::match_domstring_ascii;
43use script_bindings::num::Finite;
44use script_bindings::reflector::DomObject;
45use script_bindings::root::{Dom, DomRoot, DomSlice};
46use script_bindings::script_runtime::CanGc;
47use script_bindings::str::DOMString;
48use script_traits::ConstellationInputEvent;
49use servo_base::generic_channel::GenericCallback;
50use servo_config::pref;
51use servo_constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
52use style::Atom;
53use style_traits::CSSPixel;
54use webrender_api::ExternalScrollId;
55
56use crate::dom::bindings::cell::DomRefCell;
57use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
58use crate::dom::bindings::refcounted::Trusted;
59use crate::dom::bindings::root::MutNullableDom;
60use crate::dom::bindings::trace::NoTrace;
61use crate::dom::clipboardevent::ClipboardEventType;
62use crate::dom::document::FireMouseEventType;
63use crate::dom::document::focus::{FocusInitiator, FocusOperation, FocusableArea};
64use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
65#[cfg(feature = "gamepad")]
66use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
67#[cfg(feature = "gamepad")]
68use crate::dom::gamepad::gamepadevent::GamepadEventType;
69use crate::dom::inputevent::HitTestResult;
70use crate::dom::interactive_element_command::InteractiveElementCommand;
71use crate::dom::keyboardevent::KeyboardEvent;
72use crate::dom::node::{self, Node, NodeTraits, ShadowIncluding};
73use crate::dom::pointerevent::{PointerEvent, PointerId};
74use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
75use crate::dom::types::{
76 ClipboardEvent, CompositionEvent, DataTransfer, Element, Event, EventTarget, GlobalScope,
77 HTMLAnchorElement, HTMLElement, HTMLLabelElement, MouseEvent, Touch, TouchEvent, TouchList,
78 WheelEvent, Window,
79};
80use crate::drag_data_store::{DragDataStore, Kind, Mode};
81use crate::realms::enter_realm;
82
83#[derive(Default, JSTraceable, MallocSizeOf)]
93struct ClickCountingInfo {
94 time: Option<Instant>,
95 #[no_trace]
96 point: Option<Point2D<f32, CSSPixel>>,
97 #[no_trace]
98 button: Option<MouseButton>,
99 count: usize,
100}
101
102impl ClickCountingInfo {
103 fn reset_click_count_if_necessary(
104 &mut self,
105 button: MouseButton,
106 point_in_frame: Point2D<f32, CSSPixel>,
107 ) {
108 let (Some(previous_button), Some(previous_point), Some(previous_time)) =
109 (self.button, self.point, self.time)
110 else {
111 assert_eq!(self.count, 0);
112 return;
113 };
114
115 let double_click_timeout =
116 Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
117 let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
118
119 let line = point_in_frame - previous_point;
121 let distance = (line.dot(line) as f64).sqrt();
122 if previous_button != button ||
123 Instant::now().duration_since(previous_time) > double_click_timeout ||
124 distance > double_click_distance_threshold as f64
125 {
126 self.count = 0;
127 self.time = None;
128 self.point = None;
129 }
130 }
131
132 fn increment_click_count(
133 &mut self,
134 button: MouseButton,
135 point: Point2D<f32, CSSPixel>,
136 ) -> usize {
137 self.time = Some(Instant::now());
138 self.point = Some(point);
139 self.button = Some(button);
140 self.count += 1;
141 self.count
142 }
143}
144
145#[derive(JSTraceable, MallocSizeOf)]
149#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
150pub(crate) struct DocumentEventHandler {
151 window: Dom<Window>,
153 #[no_trace]
155 #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
156 pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
157 mouse_move_event_index: DomRefCell<Option<usize>>,
159 #[no_trace]
161 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
162 coalesced_move_event_ids: DomRefCell<Vec<InputEventId>>,
163 wheel_event_index: DomRefCell<Option<usize>>,
168 #[no_trace]
170 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
171 coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
172 click_counting_info: DomRefCell<ClickCountingInfo>,
174 #[no_trace]
175 last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
176 down_button_count: Cell<u32>,
179 current_hover_target: MutNullableDom<Element>,
181 most_recently_clicked_element: MutNullableDom<Element>,
183 #[no_trace]
185 most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
186 #[no_trace]
189 current_cursor: Cell<Option<Cursor>>,
190 active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
192 #[no_trace]
194 active_keyboard_modifiers: Cell<Modifiers>,
195 active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
197 next_touch_pointer_id: Cell<i32>,
199 access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
201 sequential_focus_navigation_starting_point: MutNullableDom<Node>,
203}
204
205impl DocumentEventHandler {
206 pub(crate) fn new(window: &Window) -> Self {
207 Self {
208 window: Dom::from_ref(window),
209 pending_input_events: Default::default(),
210 mouse_move_event_index: Default::default(),
211 coalesced_move_event_ids: Default::default(),
212 wheel_event_index: Default::default(),
213 coalesced_wheel_event_ids: Default::default(),
214 click_counting_info: Default::default(),
215 last_mouse_button_down_point: Default::default(),
216 down_button_count: Cell::new(0),
217 current_hover_target: 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 sequential_focus_navigation_starting_point: Default::default(),
227 }
228 }
229
230 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
232 let mut pending_input_events = self.pending_input_events.borrow_mut();
233 if matches!(event.event.event, InputEvent::MouseMove(..)) {
234 if let Some(mouse_move_event) = self
236 .mouse_move_event_index
237 .borrow()
238 .and_then(|index| pending_input_events.get_mut(index))
239 {
240 self.coalesced_move_event_ids
241 .borrow_mut()
242 .push(mouse_move_event.event.id);
243 *mouse_move_event = event;
244 return;
245 }
246
247 *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
248 }
249
250 if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
251 if let Some(existing_constellation_wheel_event) = self
253 .wheel_event_index
254 .borrow()
255 .and_then(|index| pending_input_events.get_mut(index))
256 {
257 if let InputEvent::Wheel(ref mut existing_wheel_event) =
258 existing_constellation_wheel_event.event.event
259 {
260 if existing_wheel_event.delta.mode == new_wheel_event.delta.mode {
261 self.coalesced_wheel_event_ids
262 .borrow_mut()
263 .push(existing_constellation_wheel_event.event.id);
264 existing_wheel_event.delta.x += new_wheel_event.delta.x;
265 existing_wheel_event.delta.y += new_wheel_event.delta.y;
266 existing_wheel_event.delta.z += new_wheel_event.delta.z;
267 existing_wheel_event.point = new_wheel_event.point;
268 existing_constellation_wheel_event.event.id = event.event.id;
269 return;
270 }
271 }
272 }
273
274 *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
275 }
276
277 pending_input_events.push(event);
278 }
279
280 pub(crate) fn has_pending_input_events(&self) -> bool {
283 !self.pending_input_events.borrow().is_empty()
284 }
285
286 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
287 #[cfg(target_os = "macos")]
288 {
289 self.active_keyboard_modifiers
290 .get()
291 .contains(Modifiers::META)
292 }
293 #[cfg(not(target_os = "macos"))]
294 {
295 self.active_keyboard_modifiers
296 .get()
297 .contains(Modifiers::CONTROL)
298 }
299 }
300
301 pub(crate) fn handle_pending_input_events(&self, cx: &mut JSContext) {
302 debug_assert!(
303 !self.pending_input_events.borrow().is_empty(),
304 "handle_pending_input_events called with no events"
305 );
306 let _realm = enter_realm(&*self.window);
307
308 *self.mouse_move_event_index.borrow_mut() = None;
310 *self.wheel_event_index.borrow_mut() = None;
311 let pending_input_events = mem::take(&mut *self.pending_input_events.borrow_mut());
312 let mut coalesced_move_event_ids =
313 mem::take(&mut *self.coalesced_move_event_ids.borrow_mut());
314 let mut coalesced_wheel_event_ids =
315 mem::take(&mut *self.coalesced_wheel_event_ids.borrow_mut());
316
317 let mut input_event_outcomes = Vec::with_capacity(
318 pending_input_events.len() +
319 coalesced_move_event_ids.len() +
320 coalesced_wheel_event_ids.len(),
321 );
322 for event in pending_input_events {
328 self.active_keyboard_modifiers
329 .set(event.active_keyboard_modifiers);
330 let result = match event.event.event {
331 InputEvent::MouseButton(mouse_button_event) => {
332 self.handle_native_mouse_button_event(cx, mouse_button_event, &event);
333 InputEventResult::default()
334 },
335 InputEvent::MouseMove(_) => {
336 self.handle_native_mouse_move_event(cx, &event);
337 input_event_outcomes.extend(
338 mem::take(&mut coalesced_move_event_ids)
339 .into_iter()
340 .map(|id| InputEventOutcome {
341 id,
342 result: InputEventResult::default(),
343 }),
344 );
345 InputEventResult::default()
346 },
347 InputEvent::MouseLeftViewport(mouse_leave_event) => {
348 self.handle_mouse_left_viewport_event(cx, &event, &mouse_leave_event);
349 InputEventResult::default()
350 },
351 InputEvent::Touch(touch_event) => self.handle_touch_event(cx, touch_event, &event),
352 InputEvent::Wheel(wheel_event) => {
353 let result = self.handle_wheel_event(cx, wheel_event, &event);
354 input_event_outcomes.extend(
355 mem::take(&mut coalesced_wheel_event_ids)
356 .into_iter()
357 .map(|id| InputEventOutcome { id, result }),
358 );
359 result
360 },
361 InputEvent::Keyboard(keyboard_event) => {
362 self.handle_keyboard_event(cx, keyboard_event)
363 },
364 InputEvent::Ime(ime_event) => self.handle_ime_event(cx, ime_event),
365 #[cfg(feature = "gamepad")]
366 InputEvent::Gamepad(gamepad_event) => {
367 self.handle_gamepad_event(gamepad_event);
368 InputEventResult::default()
369 },
370 InputEvent::EditingAction(editing_action_event) => {
371 self.handle_editing_action(cx, None, editing_action_event)
372 },
373 };
374
375 input_event_outcomes.push(InputEventOutcome {
376 id: event.event.id,
377 result,
378 });
379 }
380
381 self.notify_embedder_that_events_were_handled(input_event_outcomes);
382 }
383
384 fn notify_embedder_that_events_were_handled(
385 &self,
386 input_event_outcomes: Vec<InputEventOutcome>,
387 ) {
388 let trusted_window = Trusted::new(&*self.window);
391 self.window
392 .as_global_scope()
393 .task_manager()
394 .dom_manipulation_task_source()
395 .queue(task!(notify_webdriver_input_event_completed: move || {
396 let window = trusted_window.root();
397 window.send_to_embedder(
398 EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
399 }));
400 }
401
402 fn target_for_events_following_focus(&self) -> DomRoot<EventTarget> {
406 let document = self.window.Document();
407 match &*document.focus_handler().focused_area() {
408 FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
409 FocusableArea::Viewport => document
410 .GetBody()
411 .map(DomRoot::upcast)
412 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast())),
413 }
414 }
415
416 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
417 if cursor == self.current_cursor.get() {
418 return;
419 }
420 self.current_cursor.set(cursor);
421 self.window.send_to_embedder(EmbedderMsg::SetCursor(
422 self.window.webview_id(),
423 cursor.unwrap_or_default(),
424 ));
425 }
426
427 fn handle_mouse_left_viewport_event(
428 &self,
429 cx: &mut JSContext,
430 input_event: &ConstellationInputEvent,
431 mouse_leave_event: &MouseLeftViewportEvent,
432 ) {
433 if let Some(current_hover_target) = self.current_hover_target.get() {
434 let current_hover_target = current_hover_target.upcast::<Node>();
435 for element in current_hover_target
436 .inclusive_ancestors(ShadowIncluding::Yes)
437 .filter_map(DomRoot::downcast::<Element>)
438 {
439 element.set_hover_state(false);
440 self.element_for_activation(element).set_active_state(false);
441 }
442
443 if let Some(hit_test_result) = self
444 .most_recent_mousemove_point
445 .get()
446 .and_then(|point| self.window.hit_test_from_point_in_viewport(point))
447 {
448 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
449 cx,
450 &self.window,
451 FireMouseEventType::Out,
452 &hit_test_result,
453 input_event,
454 );
455
456 mouse_out_event
458 .to_pointer_hover_event("pointerout", CanGc::from_cx(cx))
459 .upcast::<Event>()
460 .fire(current_hover_target.upcast(), CanGc::from_cx(cx));
461
462 mouse_out_event
463 .upcast::<Event>()
464 .fire(current_hover_target.upcast(), CanGc::from_cx(cx));
465
466 self.handle_mouse_enter_leave_event(
467 cx,
468 DomRoot::from_ref(current_hover_target),
469 None,
470 FireMouseEventType::Leave,
471 &hit_test_result,
472 input_event,
473 );
474 }
475 }
476
477 if !mouse_leave_event.focus_moving_to_another_iframe {
482 self.window
486 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
487 self.set_cursor(None);
488 } else {
489 self.current_cursor.set(None);
490 }
491
492 self.current_hover_target.set(None);
493 self.most_recent_mousemove_point.set(None);
494 }
495
496 fn handle_mouse_enter_leave_event(
497 &self,
498 cx: &mut JSContext,
499 event_target: DomRoot<Node>,
500 related_target: Option<DomRoot<Node>>,
501 event_type: FireMouseEventType,
502 hit_test_result: &HitTestResult,
503 input_event: &ConstellationInputEvent,
504 ) {
505 assert!(matches!(
506 event_type,
507 FireMouseEventType::Enter | FireMouseEventType::Leave
508 ));
509
510 let common_ancestor = match related_target.as_ref() {
511 Some(related_target) => event_target
512 .common_ancestor_in_flat_tree(related_target)
513 .unwrap_or_else(|| DomRoot::from_ref(&*event_target)),
514 None => DomRoot::from_ref(&*event_target),
515 };
516
517 let mut targets = vec![];
520 let mut current = Some(event_target);
521 while let Some(node) = current {
522 if node == common_ancestor {
523 break;
524 }
525 current = node.parent_in_flat_tree();
526 targets.push(node);
527 }
528
529 if event_type == FireMouseEventType::Enter {
532 targets = targets.into_iter().rev().collect();
533 }
534
535 let pointer_event_name = match event_type {
536 FireMouseEventType::Enter => "pointerenter",
537 FireMouseEventType::Leave => "pointerleave",
538 _ => unreachable!(),
539 };
540
541 for target in targets {
542 let mouse_event = MouseEvent::new_for_platform_motion_event(
543 cx,
544 &self.window,
545 event_type,
546 hit_test_result,
547 input_event,
548 );
549 mouse_event
550 .upcast::<Event>()
551 .set_related_target(related_target.as_ref().map(|target| target.upcast()));
552
553 mouse_event
555 .to_pointer_hover_event(pointer_event_name, CanGc::from_cx(cx))
556 .upcast::<Event>()
557 .fire(target.upcast(), CanGc::from_cx(cx));
558
559 mouse_event
561 .upcast::<Event>()
562 .fire(target.upcast(), CanGc::from_cx(cx));
563 }
564 }
565
566 fn handle_native_mouse_move_event(
568 &self,
569 cx: &mut JSContext,
570 input_event: &ConstellationInputEvent,
571 ) {
572 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
574 return;
575 };
576
577 let old_mouse_move_point = self
578 .most_recent_mousemove_point
579 .replace(Some(hit_test_result.point_in_frame));
580 if old_mouse_move_point == Some(hit_test_result.point_in_frame) {
581 return;
582 }
583
584 self.set_cursor(Some(hit_test_result.cursor));
586
587 let Some(new_target) = hit_test_result
588 .node
589 .inclusive_ancestors(ShadowIncluding::Yes)
590 .find_map(DomRoot::downcast::<Element>)
591 else {
592 return;
593 };
594
595 let old_hover_target = self.current_hover_target.get();
596 let target_has_changed = old_hover_target
597 .as_ref()
598 .is_none_or(|old_target| *old_target != new_target);
599
600 if target_has_changed {
603 if let Some(old_target) = self.current_hover_target.get() {
605 let old_target_is_ancestor_of_new_target = old_target
606 .upcast::<Node>()
607 .is_ancestor_of(new_target.upcast::<Node>());
608
609 if !old_target_is_ancestor_of_new_target {
612 for element in old_target
613 .upcast::<Node>()
614 .inclusive_ancestors(ShadowIncluding::No)
615 .filter_map(DomRoot::downcast::<Element>)
616 {
617 element.set_hover_state(false);
618 self.element_for_activation(element).set_active_state(false);
619 }
620 }
621
622 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
623 cx,
624 &self.window,
625 FireMouseEventType::Out,
626 &hit_test_result,
627 input_event,
628 );
629 mouse_out_event
630 .upcast::<Event>()
631 .set_related_target(Some(new_target.upcast()));
632
633 mouse_out_event
635 .to_pointer_hover_event("pointerout", CanGc::from_cx(cx))
636 .upcast::<Event>()
637 .fire(old_target.upcast(), CanGc::from_cx(cx));
638
639 mouse_out_event
640 .upcast::<Event>()
641 .fire(old_target.upcast(), CanGc::from_cx(cx));
642
643 if !old_target_is_ancestor_of_new_target {
644 let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
645 let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
646 self.handle_mouse_enter_leave_event(
647 cx,
648 event_target,
649 moving_into,
650 FireMouseEventType::Leave,
651 &hit_test_result,
652 input_event,
653 );
654 }
655 }
656
657 for element in new_target
659 .upcast::<Node>()
660 .inclusive_ancestors(ShadowIncluding::Yes)
661 .filter_map(DomRoot::downcast::<Element>)
662 {
663 element.set_hover_state(true);
664 }
665
666 let mouse_over_event = MouseEvent::new_for_platform_motion_event(
667 cx,
668 &self.window,
669 FireMouseEventType::Over,
670 &hit_test_result,
671 input_event,
672 );
673 mouse_over_event
674 .upcast::<Event>()
675 .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
676
677 mouse_over_event
679 .to_pointer_hover_event("pointerover", CanGc::from_cx(cx))
680 .upcast::<Event>()
681 .dispatch(new_target.upcast(), false, CanGc::from_cx(cx));
682
683 mouse_over_event.upcast::<Event>().dispatch(
684 new_target.upcast(),
685 false,
686 CanGc::from_cx(cx),
687 );
688
689 let moving_from =
690 old_hover_target.map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
691 let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
692 self.handle_mouse_enter_leave_event(
693 cx,
694 event_target,
695 moving_from,
696 FireMouseEventType::Enter,
697 &hit_test_result,
698 input_event,
699 );
700 }
701
702 let mouse_event = MouseEvent::new_for_platform_motion_event(
705 cx,
706 &self.window,
707 FireMouseEventType::Move,
708 &hit_test_result,
709 input_event,
710 );
711
712 let pointer_event =
714 mouse_event.to_pointer_event(Atom::from("pointermove"), CanGc::from_cx(cx));
715 pointer_event.upcast::<Event>().set_composed(true);
716 pointer_event
717 .upcast::<Event>()
718 .fire(new_target.upcast(), CanGc::from_cx(cx));
719
720 mouse_event
723 .upcast::<Event>()
724 .fire(new_target.upcast(), CanGc::from_cx(cx));
725
726 self.update_current_hover_target_and_status(Some(new_target));
727 }
728
729 fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
730 let current_hover_target = self.current_hover_target.get();
731 if current_hover_target == new_hover_target {
732 return;
733 }
734
735 let previous_hover_target = self.current_hover_target.get();
736 self.current_hover_target.set(new_hover_target.as_deref());
737
738 if let Some(target) = self.current_hover_target.get() {
741 if let Some(anchor) = target
742 .upcast::<Node>()
743 .inclusive_ancestors(ShadowIncluding::Yes)
744 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
745 {
746 let status = anchor
747 .full_href_url_for_user_interface()
748 .map(|url| url.to_string());
749 self.window
750 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
751 return;
752 }
753 }
754
755 if previous_hover_target.is_none_or(|previous_hover_target| {
760 previous_hover_target
761 .upcast::<Node>()
762 .inclusive_ancestors(ShadowIncluding::Yes)
763 .any(|node| node.is::<HTMLAnchorElement>())
764 }) {
765 self.window
766 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
767 }
768 }
769
770 pub(crate) fn handle_refresh_cursor(&self) {
771 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
772 return;
773 };
774
775 let Some(hit_test_result) = self
776 .window
777 .hit_test_from_point_in_viewport(most_recent_mousemove_point)
778 else {
779 return;
780 };
781
782 self.set_cursor(Some(hit_test_result.cursor));
783 }
784
785 fn element_for_activation(&self, element: DomRoot<Element>) -> DomRoot<Element> {
786 let node: &Node = element.upcast();
787 if node.is_in_ua_widget() {
788 if let Some(containing_shadow_root) = node.containing_shadow_root() {
789 return containing_shadow_root.Host();
790 }
791 }
792
793 if node.type_id() ==
795 NodeTypeId::Element(ElementTypeId::HTMLElement(
796 HTMLElementTypeId::HTMLLabelElement,
797 ))
798 {
799 let label = element.downcast::<HTMLLabelElement>().unwrap();
800 if let Some(control) = label.GetControl() {
801 return DomRoot::from_ref(control.upcast::<Element>());
802 }
803 }
804
805 element
806 }
807
808 fn handle_native_mouse_button_event(
811 &self,
812 cx: &mut JSContext,
813 event: MouseButtonEvent,
814 input_event: &ConstellationInputEvent,
815 ) {
816 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
818 return;
819 };
820
821 debug!(
822 "{:?}: at {:?}",
823 event.action, hit_test_result.point_in_frame
824 );
825
826 if event.action == MouseButtonAction::Down {
829 self.set_sequential_focus_navigation_starting_point(&hit_test_result.node);
830 }
831
832 let Some(element) = hit_test_result
833 .node
834 .inclusive_ancestors(ShadowIncluding::Yes)
835 .find_map(DomRoot::downcast::<Element>)
836 else {
837 return;
838 };
839
840 let node = element.upcast::<Node>();
841 debug!("{:?} on {:?}", event.action, node.debug_str());
842
843 if event.action == MouseButtonAction::Down {
847 self.element_for_activation(element.clone())
848 .set_active_state(true);
849 }
850 if event.action == MouseButtonAction::Up {
851 self.element_for_activation(element.clone())
852 .set_active_state(false);
853 }
854
855 if element.is_actually_disabled() {
859 return;
860 }
861
862 let mouse_event_type = match event.action {
863 embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
864 embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
865 };
866
867 if event.action == MouseButtonAction::Down {
874 self.click_counting_info
875 .borrow_mut()
876 .reset_click_count_if_necessary(event.button, hit_test_result.point_in_frame);
877 }
878
879 let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_button_event(
880 cx,
881 mouse_event_type,
882 event,
883 input_event.pressed_mouse_buttons,
884 &self.window,
885 &hit_test_result,
886 input_event.active_keyboard_modifiers,
887 self.click_counting_info.borrow().count + 1,
888 ));
889
890 match event.action {
891 MouseButtonAction::Down => {
892 self.last_mouse_button_down_point
893 .set(Some(hit_test_result.point_in_frame));
894
895 let down_button_count = self.down_button_count.get();
897
898 let event_type = if down_button_count == 0 {
899 "pointerdown"
900 } else {
901 "pointermove"
902 };
903 let pointer_event = dom_event
904 .downcast::<MouseEvent>()
905 .unwrap()
906 .to_pointer_event(event_type.into(), CanGc::from_cx(cx));
907
908 pointer_event
909 .upcast::<Event>()
910 .fire(node.upcast(), CanGc::from_cx(cx));
911
912 self.down_button_count.set(down_button_count + 1);
913
914 let result = dom_event.dispatch(node.upcast(), false, CanGc::from_cx(cx));
916
917 if result {
920 self.window.Document().focus_handler().focus(
924 FocusOperation::Focus(node.find_click_focusable_area()),
925 FocusInitiator::Local,
926 CanGc::from_cx(cx),
927 );
928 }
929
930 if let MouseButton::Right = event.button {
933 self.maybe_show_context_menu(
934 node.upcast(),
935 &hit_test_result,
936 input_event,
937 CanGc::from_cx(cx),
938 );
939 }
940 },
941 MouseButtonAction::Up => {
943 let down_button_count = self.down_button_count.get();
945
946 if down_button_count > 0 {
947 self.down_button_count.set(down_button_count - 1);
948 }
949
950 let event_type = if down_button_count == 0 {
951 "pointerup"
952 } else {
953 "pointermove"
954 };
955 let pointer_event = dom_event
956 .downcast::<MouseEvent>()
957 .unwrap()
958 .to_pointer_event(event_type.into(), CanGc::from_cx(cx));
959
960 pointer_event
961 .upcast::<Event>()
962 .fire(node.upcast(), CanGc::from_cx(cx));
963
964 dom_event.dispatch(node.upcast(), false, CanGc::from_cx(cx));
966
967 self.click_counting_info
971 .borrow_mut()
972 .increment_click_count(event.button, hit_test_result.point_in_frame);
973
974 self.maybe_trigger_click_for_mouse_button_down_event(
975 cx,
976 event,
977 input_event,
978 &hit_test_result,
979 &element,
980 );
981 },
982 }
983 }
984
985 fn maybe_trigger_click_for_mouse_button_down_event(
988 &self,
989 cx: &mut JSContext,
990 event: MouseButtonEvent,
991 input_event: &ConstellationInputEvent,
992 hit_test_result: &HitTestResult,
993 element: &Element,
994 ) {
995 if event.button != MouseButton::Left {
996 return;
997 }
998
999 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
1000 return;
1001 };
1002
1003 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
1004 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
1005 if distance > maximum_click_distance {
1006 return;
1007 }
1008
1009 let element = match hit_test_result.node.find_click_focusable_area() {
1016 FocusableArea::Node { node, .. } => DomRoot::downcast::<Element>(node),
1017 _ => None,
1018 }
1019 .unwrap_or_else(|| DomRoot::from_ref(element));
1020 self.most_recently_clicked_element.set(Some(&*element));
1021
1022 let click_count = self.click_counting_info.borrow().count;
1023 element.set_click_in_progress(true);
1024 MouseEvent::for_platform_button_event(
1025 cx,
1026 atom!("click"),
1027 event,
1028 input_event.pressed_mouse_buttons,
1029 &self.window,
1030 hit_test_result,
1031 input_event.active_keyboard_modifiers,
1032 click_count,
1033 )
1034 .upcast::<Event>()
1035 .dispatch(element.upcast(), false, CanGc::from_cx(cx));
1036 element.set_click_in_progress(false);
1037
1038 if click_count % 2 == 0 {
1047 MouseEvent::for_platform_button_event(
1048 cx,
1049 Atom::from("dblclick"),
1050 event,
1051 input_event.pressed_mouse_buttons,
1052 &self.window,
1053 hit_test_result,
1054 input_event.active_keyboard_modifiers,
1055 2,
1056 )
1057 .upcast::<Event>()
1058 .dispatch(element.upcast(), false, CanGc::from_cx(cx));
1059 }
1060 }
1061
1062 fn maybe_show_context_menu(
1064 &self,
1065 target: &EventTarget,
1066 hit_test_result: &HitTestResult,
1067 input_event: &ConstellationInputEvent,
1068 can_gc: CanGc,
1069 ) {
1070 let menu_event = PointerEvent::new(
1072 &self.window, "contextmenu".into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
1079 hit_test_result.point_in_frame.to_i32(),
1080 hit_test_result
1081 .point_relative_to_initial_containing_block
1082 .to_i32(),
1083 input_event.active_keyboard_modifiers,
1084 2i16, input_event.pressed_mouse_buttons,
1086 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![], can_gc,
1103 );
1104 menu_event.upcast::<Event>().set_composed(true);
1105
1106 let result = menu_event.upcast::<Event>().fire(target, can_gc);
1108
1109 if result {
1111 self.window
1112 .Document()
1113 .embedder_controls()
1114 .show_context_menu(hit_test_result);
1115 };
1116 }
1117
1118 fn handle_touch_event(
1119 &self,
1120 cx: &mut JSContext,
1121 event: EmbedderTouchEvent,
1122 input_event: &ConstellationInputEvent,
1123 ) -> InputEventResult {
1124 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1126 self.update_active_touch_points_when_early_return(event);
1127 return Default::default();
1128 };
1129
1130 let TouchId(identifier) = event.touch_id;
1131
1132 let Some(element) = hit_test_result
1133 .node
1134 .inclusive_ancestors(ShadowIncluding::Yes)
1135 .find_map(DomRoot::downcast::<Element>)
1136 else {
1137 self.update_active_touch_points_when_early_return(event);
1138 return Default::default();
1139 };
1140
1141 let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1142 let window = &*self.window;
1143
1144 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1145 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1146 let page_x =
1147 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1148 let page_y =
1149 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1150
1151 let pointer_touch = Touch::new(
1153 window,
1154 identifier,
1155 ¤t_target,
1156 client_x,
1157 client_y, client_x,
1159 client_y,
1160 page_x,
1161 page_y,
1162 CanGc::from_cx(cx),
1163 );
1164
1165 let pointer_event_name = match event.event_type {
1167 TouchEventType::Down => "pointerdown",
1168 TouchEventType::Move => "pointermove",
1169 TouchEventType::Up => "pointerup",
1170 TouchEventType::Cancel => "pointercancel",
1171 };
1172
1173 let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1175 let is_primary = self.is_primary_pointer(pointer_id);
1176
1177 if matches!(event.event_type, TouchEventType::Down) {
1180 let pointer_over = pointer_touch.to_pointer_event(
1182 window,
1183 "pointerover",
1184 pointer_id,
1185 is_primary,
1186 input_event.active_keyboard_modifiers,
1187 true, Some(hit_test_result.point_in_node),
1189 CanGc::from_cx(cx),
1190 );
1191 pointer_over
1192 .upcast::<Event>()
1193 .fire(¤t_target, CanGc::from_cx(cx));
1194
1195 self.fire_pointer_event_for_touch(
1197 &element,
1198 &pointer_touch,
1199 pointer_id,
1200 "pointerenter",
1201 is_primary,
1202 input_event,
1203 &hit_test_result,
1204 CanGc::from_cx(cx),
1205 );
1206 }
1207
1208 let pointer_event = pointer_touch.to_pointer_event(
1209 window,
1210 pointer_event_name,
1211 pointer_id,
1212 is_primary,
1213 input_event.active_keyboard_modifiers,
1214 event.is_cancelable(),
1215 Some(hit_test_result.point_in_node),
1216 CanGc::from_cx(cx),
1217 );
1218 pointer_event
1219 .upcast::<Event>()
1220 .fire(¤t_target, CanGc::from_cx(cx));
1221
1222 if matches!(
1225 event.event_type,
1226 TouchEventType::Up | TouchEventType::Cancel
1227 ) {
1228 let pointer_out = pointer_touch.to_pointer_event(
1230 window,
1231 "pointerout",
1232 pointer_id,
1233 is_primary,
1234 input_event.active_keyboard_modifiers,
1235 true, Some(hit_test_result.point_in_node),
1237 CanGc::from_cx(cx),
1238 );
1239 pointer_out
1240 .upcast::<Event>()
1241 .fire(¤t_target, CanGc::from_cx(cx));
1242
1243 self.fire_pointer_event_for_touch(
1245 &element,
1246 &pointer_touch,
1247 pointer_id,
1248 "pointerleave",
1249 is_primary,
1250 input_event,
1251 &hit_test_result,
1252 CanGc::from_cx(cx),
1253 );
1254 }
1255
1256 let (touch_dispatch_target, changed_touch) = match event.event_type {
1257 TouchEventType::Down => {
1258 self.active_touch_points
1260 .borrow_mut()
1261 .push(Dom::from_ref(&*pointer_touch));
1262 self.element_for_activation(element).set_active_state(true);
1265 (current_target, pointer_touch)
1266 },
1267 _ => {
1268 let mut active_touch_points = self.active_touch_points.borrow_mut();
1274 let Some(index) = active_touch_points
1275 .iter()
1276 .position(|point| point.Identifier() == identifier)
1277 else {
1278 warn!("No active touch point for {:?}", event.event_type);
1279 return Default::default();
1280 };
1281 let original_target = active_touch_points[index].Target();
1283
1284 let touch_with_touchstart_target = Touch::new(
1285 window,
1286 identifier,
1287 &original_target,
1288 client_x,
1289 client_y,
1290 client_x,
1291 client_y,
1292 page_x,
1293 page_y,
1294 CanGc::from_cx(cx),
1295 );
1296
1297 match event.event_type {
1299 TouchEventType::Move => {
1300 active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1301 },
1302 TouchEventType::Up | TouchEventType::Cancel => {
1303 active_touch_points.swap_remove(index);
1304 self.remove_pointer_id_for_touch(identifier);
1305 self.element_for_activation(element).set_active_state(false);
1308 },
1309 TouchEventType::Down => unreachable!("Should have been handled above"),
1310 }
1311 (original_target, touch_with_touchstart_target)
1312 },
1313 };
1314
1315 rooted_vec!(let mut target_touches);
1316 target_touches.extend(
1317 self.active_touch_points
1318 .borrow()
1319 .iter()
1320 .filter(|touch| touch.Target() == touch_dispatch_target)
1321 .cloned(),
1322 );
1323
1324 let event_name = match event.event_type {
1325 TouchEventType::Down => "touchstart",
1326 TouchEventType::Move => "touchmove",
1327 TouchEventType::Up => "touchend",
1328 TouchEventType::Cancel => "touchcancel",
1329 };
1330
1331 let touch_event = TouchEvent::new(
1332 window,
1333 event_name.into(),
1334 EventBubbles::Bubbles,
1335 EventCancelable::from(event.is_cancelable()),
1336 EventComposed::Composed,
1337 Some(window),
1338 0i32,
1339 &TouchList::new(
1340 window,
1341 self.active_touch_points.borrow().r(),
1342 CanGc::from_cx(cx),
1343 ),
1344 &TouchList::new(window, from_ref(&&*changed_touch), CanGc::from_cx(cx)),
1345 &TouchList::new(window, target_touches.r(), CanGc::from_cx(cx)),
1346 false,
1348 false,
1349 false,
1350 false,
1351 CanGc::from_cx(cx),
1352 );
1353 let event = touch_event.upcast::<Event>();
1354 event.fire(&touch_dispatch_target, CanGc::from_cx(cx));
1355 event.flags().into()
1356 }
1357
1358 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1367 match event.event_type {
1368 TouchEventType::Down | TouchEventType::Move => {},
1369 TouchEventType::Up | TouchEventType::Cancel => {
1370 let mut active_touch_points = self.active_touch_points.borrow_mut();
1371 if let Some(index) = active_touch_points
1372 .iter()
1373 .position(|t| t.Identifier() == event.touch_id.0)
1374 {
1375 active_touch_points.swap_remove(index);
1376 self.remove_pointer_id_for_touch(event.touch_id.0);
1377 } else {
1378 warn!(
1379 "Received {:?} for a non-active touch point {}",
1380 event.event_type, event.touch_id.0
1381 );
1382 }
1383 },
1384 }
1385 }
1386
1387 fn handle_keyboard_event(
1389 &self,
1390 cx: &mut JSContext,
1391 keyboard_event: EmbedderKeyboardEvent,
1392 ) -> InputEventResult {
1393 let target = &self.target_for_events_following_focus();
1394 let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1395 cx,
1396 &self.window,
1397 keyboard_event.event.state.event_type().into(),
1398 &keyboard_event.event,
1399 );
1400
1401 let event = keyevent.upcast::<Event>();
1402
1403 event.set_composed(true);
1404
1405 event.fire(target, CanGc::from_cx(cx));
1406
1407 let mut flags = event.flags();
1408 if flags.contains(EventFlags::Canceled) {
1409 return flags.into();
1410 }
1411
1412 let is_character_value_key = matches!(
1418 keyboard_event.event.key,
1419 Key::Character(_) | Key::Named(NamedKey::Enter)
1420 );
1421 if keyboard_event.event.state == KeyState::Down &&
1422 is_character_value_key &&
1423 !keyboard_event.event.is_composing
1424 {
1425 let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1427 cx,
1428 &self.window,
1429 atom!("keypress"),
1430 &keyboard_event.event,
1431 );
1432 keypress_event.upcast::<Event>().set_composed(true);
1433 let event = keypress_event.upcast::<Event>();
1434 event.fire(target, CanGc::from_cx(cx));
1435 flags = event.flags();
1436 }
1437
1438 flags.into()
1439 }
1440
1441 fn handle_ime_event(&self, cx: &mut JSContext, event: ImeEvent) -> InputEventResult {
1442 let document = self.window.Document();
1443 let composition_event = match event {
1444 ImeEvent::Dismissed => {
1445 document.focus_handler().focus(
1446 FocusOperation::Focus(FocusableArea::Viewport),
1447 FocusInitiator::Local,
1448 CanGc::from_cx(cx),
1449 );
1450 return Default::default();
1451 },
1452 ImeEvent::Composition(composition_event) => composition_event,
1453 };
1454
1455 let focused_area = document.focus_handler().focused_area();
1460 let Some(focused_element) = focused_area.element() else {
1461 return Default::default();
1463 };
1464
1465 let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1466 let event = CompositionEvent::new(
1467 &self.window,
1468 composition_event.state.event_type().into(),
1469 true,
1470 cancelable,
1471 Some(&self.window),
1472 0,
1473 DOMString::from(composition_event.data),
1474 CanGc::from_cx(cx),
1475 );
1476
1477 let event = event.upcast::<Event>();
1478 event.fire(focused_element.upcast(), CanGc::from_cx(cx));
1479 event.flags().into()
1480 }
1481
1482 fn handle_wheel_event(
1483 &self,
1484 cx: &mut JSContext,
1485 event: EmbedderWheelEvent,
1486 input_event: &ConstellationInputEvent,
1487 ) -> InputEventResult {
1488 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1490 return Default::default();
1491 };
1492
1493 let Some(el) = hit_test_result
1494 .node
1495 .inclusive_ancestors(ShadowIncluding::Yes)
1496 .find_map(DomRoot::downcast::<Element>)
1497 else {
1498 return Default::default();
1499 };
1500
1501 let node = el.upcast::<Node>();
1502 debug!(
1503 "wheel: on {:?} at {:?}",
1504 node.debug_str(),
1505 hit_test_result.point_in_frame
1506 );
1507
1508 let dom_event = WheelEvent::new(
1510 &self.window,
1511 "wheel".into(),
1512 EventBubbles::Bubbles,
1513 EventCancelable::Cancelable,
1514 Some(&self.window),
1515 0i32,
1516 hit_test_result.point_in_frame.to_i32(),
1517 hit_test_result.point_in_frame.to_i32(),
1518 hit_test_result
1519 .point_relative_to_initial_containing_block
1520 .to_i32(),
1521 input_event.active_keyboard_modifiers,
1522 0i16,
1523 input_event.pressed_mouse_buttons,
1524 None,
1525 None,
1526 Finite::wrap(-event.delta.x),
1531 Finite::wrap(-event.delta.y),
1532 Finite::wrap(-event.delta.z),
1533 event.delta.mode as u32,
1534 CanGc::from_cx(cx),
1535 );
1536
1537 let dom_event = dom_event.upcast::<Event>();
1538 dom_event.set_trusted(true);
1539 dom_event.set_composed(true);
1540 dom_event.fire(node.upcast(), CanGc::from_cx(cx));
1541
1542 dom_event.flags().into()
1543 }
1544
1545 #[cfg(feature = "gamepad")]
1546 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1547 match gamepad_event {
1548 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1549 self.handle_gamepad_connect(
1550 index.0,
1551 name,
1552 bounds.axis_bounds,
1553 bounds.button_bounds,
1554 supported_haptic_effects,
1555 );
1556 },
1557 EmbedderGamepadEvent::Disconnected(index) => {
1558 self.handle_gamepad_disconnect(index.0);
1559 },
1560 EmbedderGamepadEvent::Updated(index, update_type) => {
1561 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1562 },
1563 };
1564 }
1565
1566 #[cfg(feature = "gamepad")]
1568 fn handle_gamepad_connect(
1569 &self,
1570 _index: usize,
1574 name: String,
1575 axis_bounds: (f64, f64),
1576 button_bounds: (f64, f64),
1577 supported_haptic_effects: GamepadSupportedHapticEffects,
1578 ) {
1579 let trusted_window = Trusted::new(&*self.window);
1582 self.window
1583 .upcast::<GlobalScope>()
1584 .task_manager()
1585 .gamepad_task_source()
1586 .queue(task!(gamepad_connected: move || {
1587 let window = trusted_window.root();
1588
1589 let navigator = window.Navigator();
1590 let selected_index = navigator.select_gamepad_index();
1591 let gamepad = Gamepad::new(
1592 &window,
1593 selected_index,
1594 name,
1595 "standard".into(),
1596 axis_bounds,
1597 button_bounds,
1598 supported_haptic_effects,
1599 false,
1600 CanGc::deprecated_note(),
1601 );
1602 navigator.set_gamepad(selected_index as usize, &gamepad, CanGc::deprecated_note());
1603 }));
1604 }
1605
1606 #[cfg(feature = "gamepad")]
1608 fn handle_gamepad_disconnect(&self, index: usize) {
1609 let trusted_window = Trusted::new(&*self.window);
1610 self.window
1611 .upcast::<GlobalScope>()
1612 .task_manager()
1613 .gamepad_task_source()
1614 .queue(task!(gamepad_disconnected: move || {
1615 let window = trusted_window.root();
1616 let navigator = window.Navigator();
1617 if let Some(gamepad) = navigator.get_gamepad(index) {
1618 if window.Document().is_fully_active() {
1619 gamepad.update_connected(false, gamepad.exposed(), CanGc::deprecated_note());
1620 navigator.remove_gamepad(index);
1621 }
1622 }
1623 }));
1624 }
1625
1626 #[cfg(feature = "gamepad")]
1628 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1629 let trusted_window = Trusted::new(&*self.window);
1630
1631 self.window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1633 task!(update_gamepad_state: move || {
1634 let window = trusted_window.root();
1635 let navigator = window.Navigator();
1636 if let Some(gamepad) = navigator.get_gamepad(index) {
1637 let current_time = window.Performance().Now();
1638 gamepad.update_timestamp(*current_time);
1639 match update_type {
1640 GamepadUpdateType::Axis(index, value) => {
1641 gamepad.map_and_normalize_axes(index, value);
1642 },
1643 GamepadUpdateType::Button(index, value) => {
1644 gamepad.map_and_normalize_buttons(index, value);
1645 }
1646 };
1647 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1648 navigator.set_has_gamepad_gesture(true);
1649 navigator.GetGamepads()
1650 .iter()
1651 .filter_map(|g| g.as_ref())
1652 .for_each(|gamepad| {
1653 gamepad.set_exposed(true);
1654 gamepad.update_timestamp(*current_time);
1655 let new_gamepad = Trusted::new(&**gamepad);
1656 if window.Document().is_fully_active() {
1657 window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1658 task!(update_gamepad_connect: move || {
1659 let gamepad = new_gamepad.root();
1660 gamepad.notify_event(GamepadEventType::Connected, CanGc::deprecated_note());
1661 })
1662 );
1663 }
1664 });
1665 }
1666 }
1667 })
1668 );
1669 }
1670
1671 pub(crate) fn handle_editing_action(
1673 &self,
1674 cx: &mut JSContext,
1675 element: Option<DomRoot<Element>>,
1676 action: EditingActionEvent,
1677 ) -> InputEventResult {
1678 let clipboard_event_type = match action {
1679 EditingActionEvent::Copy => ClipboardEventType::Copy,
1680 EditingActionEvent::Cut => ClipboardEventType::Cut,
1681 EditingActionEvent::Paste => ClipboardEventType::Paste,
1682 };
1683
1684 let script_triggered = false;
1686
1687 let script_may_access_clipboard = false;
1691
1692 if script_triggered && !script_may_access_clipboard {
1694 return InputEventResult::empty();
1695 }
1696
1697 let clipboard_event =
1699 self.fire_clipboard_event(element.clone(), clipboard_event_type, CanGc::from_cx(cx));
1700
1701 let event = clipboard_event.upcast::<Event>();
1704 if !event.IsTrusted() {
1705 return event.flags().into();
1706 }
1707
1708 if event.DefaultPrevented() {
1710 let event_type = event.Type();
1711 match_domstring_ascii!(event_type,
1712
1713 "copy" => {
1714 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1717 let drag_data_store =
1718 clipboard_data.data_store().expect("This shouldn't fail");
1719 self.write_content_to_the_clipboard(&drag_data_store);
1720 }
1721 },
1722 "cut" => {
1723 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1726 let drag_data_store =
1727 clipboard_data.data_store().expect("This shouldn't fail");
1728 self.write_content_to_the_clipboard(&drag_data_store);
1729 }
1730
1731 self.fire_clipboard_event(element, ClipboardEventType::Change, CanGc::from_cx(cx));
1733 },
1734 "paste" => (),
1738 _ => (),
1739 )
1740 }
1741
1742 event.flags().into()
1745 }
1746
1747 pub(crate) fn fire_clipboard_event(
1749 &self,
1750 target: Option<DomRoot<Element>>,
1751 clipboard_event_type: ClipboardEventType,
1752 can_gc: CanGc,
1753 ) -> DomRoot<ClipboardEvent> {
1754 let clipboard_event = ClipboardEvent::new(
1755 &self.window,
1756 None,
1757 clipboard_event_type.as_str().into(),
1758 EventBubbles::Bubbles,
1759 EventCancelable::Cancelable,
1760 None,
1761 can_gc,
1762 );
1763
1764 let mut drag_data_store = DragDataStore::new();
1767
1768 let trusted = true;
1772
1773 let target = target
1775 .map(DomRoot::upcast)
1776 .unwrap_or_else(|| self.target_for_events_following_focus());
1777
1778 match clipboard_event_type {
1781 ClipboardEventType::Copy | ClipboardEventType::Cut => {
1782 drag_data_store.set_mode(Mode::ReadWrite);
1784 },
1785 ClipboardEventType::Paste => {
1786 let (callback, receiver) =
1787 GenericCallback::new_blocking().expect("Could not create callback");
1788 self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
1789 self.window.webview_id(),
1790 callback,
1791 ));
1792 let text_contents = receiver
1793 .recv()
1794 .map(Result::unwrap_or_default)
1795 .unwrap_or_default();
1796
1797 drag_data_store.set_mode(Mode::ReadOnly);
1799 if trusted {
1801 let data = DOMString::from(text_contents);
1805 let type_ = DOMString::from("text/plain");
1806 let _ = drag_data_store.add(Kind::Text { data, type_ });
1807
1808 }
1814 },
1815 ClipboardEventType::Change => (),
1816 }
1817
1818 let clipboard_event_data = DataTransfer::new(
1820 &self.window,
1821 Rc::new(RefCell::new(Some(drag_data_store))),
1822 can_gc,
1823 );
1824
1825 clipboard_event.set_clipboard_data(Some(&clipboard_event_data));
1827
1828 let event = clipboard_event.upcast::<Event>();
1830 event.set_trusted(trusted);
1831
1832 event.set_composed(true);
1834
1835 event.dispatch(&target, false, can_gc);
1837
1838 DomRoot::from(clipboard_event)
1839 }
1840
1841 fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
1843 if drag_data_store.list_len() > 0 {
1845 self.window
1847 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1848 for item in drag_data_store.iter_item_list() {
1850 match item {
1851 Kind::Text { data, .. } => {
1852 self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
1856 self.window.webview_id(),
1857 data.to_string(),
1858 ));
1859 },
1860 Kind::File { .. } => {
1861 },
1865 }
1866 }
1867 } else {
1868 if drag_data_store.clear_was_called {
1870 self.window
1872 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1873 }
1876 }
1877 }
1878
1879 #[expect(unsafe_code)]
1882 pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
1883 let document = self.window.Document();
1885 if scrolled_node.is_root() {
1886 document.handle_viewport_scroll_event();
1887 } else {
1888 let node_id = node_id_from_scroll_id(scrolled_node.0 as usize);
1892 let node = unsafe {
1893 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
1894 };
1895 let Some(element) = node
1896 .inclusive_ancestors(ShadowIncluding::Yes)
1897 .find_map(DomRoot::downcast::<Element>)
1898 else {
1899 return;
1900 };
1901
1902 element.handle_scroll_event();
1903 }
1904 }
1905
1906 pub(crate) fn maybe_dispatch_simulated_click(
1912 &self,
1913 node: &Node,
1914 event: &KeyboardEvent,
1915 can_gc: CanGc,
1916 ) -> bool {
1917 if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
1918 {
1919 return false;
1920 }
1921
1922 if node
1926 .downcast::<Element>()
1927 .and_then(Element::as_maybe_activatable)
1928 .is_none()
1929 {
1930 return false;
1931 }
1932
1933 node.fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc);
1934 true
1935 }
1936
1937 pub(crate) fn run_default_keyboard_event_handler(
1938 &self,
1939 node: &Node,
1940 event: &KeyboardEvent,
1941 can_gc: CanGc,
1942 ) {
1943 if event.upcast::<Event>().type_() != atom!("keydown") {
1944 return;
1945 }
1946
1947 if self.maybe_dispatch_simulated_click(node, event, can_gc) {
1948 return;
1949 }
1950
1951 if self.maybe_handle_accesskey(event, can_gc) {
1952 return;
1953 }
1954
1955 let mut is_space = false;
1956 let scroll = match event.key() {
1957 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
1958 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
1959 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
1960 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
1961 Key::Named(NamedKey::End) => KeyboardScroll::End,
1962 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
1963 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
1964 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
1965 Key::Character(string) if &string == " " => {
1966 is_space = true;
1967 if event.modifiers().contains(Modifiers::SHIFT) {
1968 KeyboardScroll::PageUp
1969 } else {
1970 KeyboardScroll::PageDown
1971 }
1972 },
1973 Key::Named(NamedKey::Tab) => {
1974 self.sequential_focus_navigation_via_keyboard_event(event, can_gc);
1980 return;
1981 },
1982 _ => return,
1983 };
1984
1985 if !event.modifiers().is_empty() && !is_space {
1986 return;
1987 }
1988
1989 self.do_keyboard_scroll(scroll);
1990 }
1991
1992 pub(crate) fn set_sequential_focus_navigation_starting_point(&self, node: &Node) {
1993 self.sequential_focus_navigation_starting_point
1994 .set(Some(node));
1995 }
1996
1997 pub(crate) fn sequential_focus_navigation_starting_point(&self) -> Option<DomRoot<Node>> {
1998 self.sequential_focus_navigation_starting_point
1999 .get()
2000 .filter(|node| node.is_connected())
2001 }
2002
2003 fn sequential_focus_navigation_via_keyboard_event(&self, event: &KeyboardEvent, can_gc: CanGc) {
2004 let direction = if event.modifiers().contains(Modifiers::SHIFT) {
2005 SequentialFocusDirection::Backward
2006 } else {
2007 SequentialFocusDirection::Forward
2008 };
2009
2010 self.sequential_focus_navigation(direction, can_gc);
2011 }
2012
2013 fn sequential_focus_navigation(&self, direction: SequentialFocusDirection, can_gc: CanGc) {
2015 let mut starting_point = self
2032 .window
2033 .Document()
2034 .focus_handler()
2035 .focused_area()
2036 .element()
2037 .map(|element| DomRoot::from_ref(element.upcast::<Node>()));
2038
2039 if let Some(sequential_focus_navigation_starting_point) =
2043 self.sequential_focus_navigation_starting_point()
2044 {
2045 if starting_point.as_ref().is_none_or(|starting_point| {
2046 starting_point.is_ancestor_of(&sequential_focus_navigation_starting_point)
2047 }) {
2048 starting_point = Some(sequential_focus_navigation_starting_point);
2049 }
2050 }
2051
2052 let candidate = starting_point
2066 .map(|starting_point| {
2067 self.find_element_for_tab_focus_following_element(direction, starting_point)
2068 })
2069 .unwrap_or_else(|| self.find_first_tab_focusable_element(direction));
2070
2071 if let Some(candidate) = candidate {
2073 self.focus_and_scroll_to_element_for_key_event(&candidate, can_gc);
2074 return;
2075 }
2076
2077 self.sequential_focus_navigation_starting_point.clear();
2079
2080 }
2089
2090 fn find_element_for_tab_focus_following_element(
2091 &self,
2092 direction: SequentialFocusDirection,
2093 starting_point: DomRoot<Node>,
2094 ) -> Option<DomRoot<Element>> {
2095 let root_node = self.window.Document().GetDocumentElement()?;
2096 let focused_element_tab_index = starting_point
2097 .downcast::<Element>()
2098 .and_then(Element::explicitly_set_tab_index)
2099 .unwrap_or_default();
2100 let mut winning_node_and_tab_index: Option<(DomRoot<Element>, i32)> = None;
2101 let mut saw_focused_element = false;
2102
2103 for node in root_node
2104 .upcast::<Node>()
2105 .traverse_preorder(ShadowIncluding::Yes)
2106 {
2107 if node == starting_point {
2108 saw_focused_element = true;
2109 continue;
2110 }
2111
2112 let Some(candidate_element) = DomRoot::downcast::<Element>(node) else {
2113 continue;
2114 };
2115 if !candidate_element.is_sequentially_focusable() {
2116 continue;
2117 }
2118
2119 let candidate_element_tab_index = candidate_element
2120 .explicitly_set_tab_index()
2121 .unwrap_or_default();
2122 let ordering =
2123 compare_tab_indices(focused_element_tab_index, candidate_element_tab_index);
2124 match direction {
2125 SequentialFocusDirection::Forward => {
2126 if saw_focused_element && ordering == Ordering::Equal {
2129 return Some(candidate_element);
2130 }
2131 if ordering != Ordering::Less {
2133 continue;
2134 }
2135 let Some((_, winning_tab_index)) = winning_node_and_tab_index else {
2136 if candidate_element_tab_index == focused_element_tab_index + 1 {
2140 return Some(candidate_element);
2141 }
2142
2143 winning_node_and_tab_index =
2144 Some((candidate_element, candidate_element_tab_index));
2145 continue;
2146 };
2147 if compare_tab_indices(candidate_element_tab_index, winning_tab_index) ==
2150 Ordering::Less
2151 {
2152 winning_node_and_tab_index =
2153 Some((candidate_element, candidate_element_tab_index))
2154 }
2155 },
2156 SequentialFocusDirection::Backward => {
2157 if !saw_focused_element && ordering == Ordering::Equal {
2160 winning_node_and_tab_index =
2161 Some((candidate_element, candidate_element_tab_index));
2162 continue;
2163 }
2164 if ordering != Ordering::Greater {
2166 continue;
2167 }
2168 let Some((_, winning_tab_index)) = winning_node_and_tab_index else {
2169 winning_node_and_tab_index =
2170 Some((candidate_element, candidate_element_tab_index));
2171 continue;
2172 };
2173 if compare_tab_indices(candidate_element_tab_index, winning_tab_index) !=
2177 Ordering::Less
2178 {
2179 winning_node_and_tab_index =
2180 Some((candidate_element, candidate_element_tab_index))
2181 }
2182 },
2183 }
2184 }
2185
2186 Some(winning_node_and_tab_index?.0)
2187 }
2188
2189 fn find_first_tab_focusable_element(
2190 &self,
2191 direction: SequentialFocusDirection,
2192 ) -> Option<DomRoot<Element>> {
2193 let root_node = self.window.Document().GetDocumentElement()?;
2194 let mut winning_node_and_tab_index: Option<(DomRoot<Element>, i32)> = None;
2195 for node in root_node
2196 .upcast::<Node>()
2197 .traverse_preorder(ShadowIncluding::Yes)
2198 {
2199 let Some(candidate_element) = DomRoot::downcast::<Element>(node) else {
2200 continue;
2201 };
2202 if !candidate_element.is_sequentially_focusable() {
2203 continue;
2204 }
2205
2206 let candidate_element_tab_index = candidate_element
2207 .explicitly_set_tab_index()
2208 .unwrap_or_default();
2209 match direction {
2210 SequentialFocusDirection::Forward => {
2211 if candidate_element_tab_index == 1 {
2215 return Some(candidate_element);
2216 }
2217
2218 if winning_node_and_tab_index
2221 .as_ref()
2222 .is_none_or(|(_, winning_tab_index)| {
2223 compare_tab_indices(candidate_element_tab_index, *winning_tab_index) ==
2224 Ordering::Less
2225 })
2226 {
2227 winning_node_and_tab_index =
2228 Some((candidate_element, candidate_element_tab_index));
2229 }
2230 },
2231 SequentialFocusDirection::Backward => {
2232 if winning_node_and_tab_index
2236 .as_ref()
2237 .is_none_or(|(_, winning_tab_index)| {
2238 compare_tab_indices(candidate_element_tab_index, *winning_tab_index) !=
2239 Ordering::Less
2240 })
2241 {
2242 winning_node_and_tab_index =
2243 Some((candidate_element, candidate_element_tab_index));
2244 }
2245 },
2246 }
2247 }
2248
2249 Some(winning_node_and_tab_index?.0)
2250 }
2251
2252 pub(crate) fn do_keyboard_scroll(&self, scroll: KeyboardScroll) {
2253 let scroll_axis = match scroll {
2254 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2255 _ => ScrollingBoxAxis::Y,
2256 };
2257
2258 let document = self.window.Document();
2259 let mut scrolling_box = document
2260 .focus_handler()
2261 .focused_area()
2262 .element()
2263 .or(self.most_recently_clicked_element.get().as_deref())
2264 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2265 .unwrap_or_else(|| {
2266 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2267 });
2268
2269 while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2270 if scrolling_box.is_viewport() {
2272 break;
2273 }
2274 let parent = scrolling_box.parent().unwrap_or_else(|| {
2275 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2276 });
2277 scrolling_box = parent;
2278 }
2279
2280 let calculate_current_scroll_offset_and_delta = || {
2281 const LINE_HEIGHT: f32 = 76.0;
2282 const LINE_WIDTH: f32 = 76.0;
2283
2284 let current_scroll_offset = scrolling_box.scroll_position();
2285 (
2286 current_scroll_offset,
2287 match scroll {
2288 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2289 KeyboardScroll::End => Vector2D::new(
2290 0.0,
2291 -current_scroll_offset.y + scrolling_box.content_size().height -
2292 scrolling_box.size().height,
2293 ),
2294 KeyboardScroll::PageDown => {
2295 Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
2296 },
2297 KeyboardScroll::PageUp => {
2298 Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
2299 },
2300 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2301 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2302 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2303 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2304 },
2305 )
2306 };
2307
2308 let parent_pipeline = self.window.parent_info();
2312 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2313 let (_, delta) = calculate_current_scroll_offset_and_delta();
2314 self.window
2315 .paint_api()
2316 .scroll_viewport_by_delta(self.window.webview_id(), delta);
2317 }
2318
2319 if !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2322 assert!(scrolling_box.is_viewport());
2323
2324 let window_proxy = document.window().window_proxy();
2325 if let Some(iframe) = window_proxy.frame_element() {
2326 let cx = GlobalScope::get_cx();
2329 let iframe_window = iframe.owner_window();
2330 let _ac = JSAutoRealm::new(*cx, iframe_window.reflector().get_jsobject().get());
2331 iframe_window
2332 .Document()
2333 .event_handler()
2334 .do_keyboard_scroll(scroll);
2335 } else if let Some(parent_pipeline) = parent_pipeline {
2336 document.window().send_to_constellation(
2340 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2341 );
2342 };
2343 return;
2344 }
2345
2346 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2347 scrolling_box.scroll_to(delta + current_scroll_offset, ScrollBehavior::Auto);
2348 }
2349
2350 fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2353 let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2354
2355 if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2356 return pointer_id;
2357 }
2358
2359 let pointer_id = self.next_touch_pointer_id.get();
2360 active_pointer_ids.insert(touch_id, pointer_id);
2361 self.next_touch_pointer_id.set(pointer_id + 1);
2362 pointer_id
2363 }
2364
2365 fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2367 self.active_pointer_ids.borrow_mut().remove(&touch_id);
2368 }
2369
2370 fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2373 self.active_pointer_ids
2376 .borrow()
2377 .values()
2378 .min()
2379 .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2380 }
2381
2382 #[allow(clippy::too_many_arguments)]
2386 fn fire_pointer_event_for_touch(
2387 &self,
2388 target_element: &Element,
2389 touch: &Touch,
2390 pointer_id: i32,
2391 event_name: &str,
2392 is_primary: bool,
2393 input_event: &ConstellationInputEvent,
2394 hit_test_result: &HitTestResult,
2395 can_gc: CanGc,
2396 ) {
2397 let mut targets: Vec<DomRoot<Node>> = vec![];
2399 let mut current: Option<DomRoot<Node>> = Some(DomRoot::from_ref(target_element.upcast()));
2400 while let Some(node) = current {
2401 targets.push(DomRoot::from_ref(&*node));
2402 current = node.parent_in_flat_tree();
2403 }
2404
2405 if event_name == "pointerenter" {
2407 targets.reverse();
2408 }
2409
2410 for target in targets {
2411 let pointer_event = touch.to_pointer_event(
2412 &self.window,
2413 event_name,
2414 pointer_id,
2415 is_primary,
2416 input_event.active_keyboard_modifiers,
2417 false,
2418 Some(hit_test_result.point_in_node),
2419 can_gc,
2420 );
2421 pointer_event
2422 .upcast::<Event>()
2423 .fire(target.upcast(), can_gc);
2424 }
2425 }
2426
2427 pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2428 self.access_key_handlers
2429 .borrow()
2430 .values()
2431 .any(|value| &**value == element)
2432 }
2433
2434 pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2435 self.access_key_handlers
2436 .borrow_mut()
2437 .retain(|_, value| &**value != element)
2438 }
2439
2440 pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2441 let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2442 access_key_handlers
2444 .entry(code.into())
2445 .or_insert(Dom::from_ref(element));
2446 }
2447
2448 fn maybe_handle_accesskey(&self, event: &KeyboardEvent, can_gc: CanGc) -> bool {
2449 #[cfg(target_os = "macos")]
2450 let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2451 #[cfg(not(target_os = "macos"))]
2452 let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2453
2454 if event.modifiers() != access_key_modifiers {
2455 return false;
2456 }
2457
2458 let Ok(code) = Code::from_str(&event.Code().str()) else {
2459 return false;
2460 };
2461
2462 let Some(html_element) = self
2463 .access_key_handlers
2464 .borrow()
2465 .get(&code.into())
2466 .map(|html_element| html_element.as_rooted())
2467 else {
2468 return false;
2469 };
2470
2471 let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2479 return false;
2480 };
2481
2482 if command.disabled() || command.hidden() {
2483 return false;
2484 }
2485
2486 let node = html_element.upcast::<Node>();
2487 if !node.is_connected() {
2488 return false;
2489 }
2490
2491 for node in node.inclusive_ancestors(ShadowIncluding::Yes) {
2492 if node
2493 .downcast::<HTMLElement>()
2494 .is_some_and(|html_element| html_element.Hidden())
2495 {
2496 return false;
2497 }
2498 }
2499
2500 self.focus_and_scroll_to_element_for_key_event(html_element.upcast(), can_gc);
2503 command.perform_action(can_gc);
2504 true
2505 }
2506
2507 fn focus_and_scroll_to_element_for_key_event(&self, element: &Element, can_gc: CanGc) {
2508 element
2509 .upcast::<Node>()
2510 .run_the_focusing_steps(None, can_gc);
2511 let scroll_axis = ScrollAxisState {
2512 position: ScrollLogicalPosition::Center,
2513 requirement: ScrollRequirement::IfNotVisible,
2514 };
2515 element.scroll_into_view_with_options(
2516 ScrollBehavior::Auto,
2517 scroll_axis,
2518 scroll_axis,
2519 None,
2520 None,
2521 );
2522 }
2523}
2524
2525#[derive(Clone, Copy, PartialEq)]
2531enum SequentialFocusDirection {
2532 Forward,
2533 Backward,
2534}
2535
2536fn compare_tab_indices(a: i32, b: i32) -> Ordering {
2537 if a == b {
2538 Ordering::Equal
2539 } else if a == 0 {
2540 Ordering::Greater
2541 } else if b == 0 {
2542 Ordering::Less
2543 } else {
2544 a.cmp(&b)
2545 }
2546}
2547
2548pub(crate) fn character_to_code(character: char) -> Option<Code> {
2549 Some(match character.to_ascii_lowercase() {
2550 '`' => Code::Backquote,
2551 '\\' => Code::Backslash,
2552 '[' | '{' => Code::BracketLeft,
2553 ']' | '}' => Code::BracketRight,
2554 ',' | '<' => Code::Comma,
2555 '0' => Code::Digit0,
2556 '1' => Code::Digit1,
2557 '2' => Code::Digit2,
2558 '3' => Code::Digit3,
2559 '4' => Code::Digit4,
2560 '5' => Code::Digit5,
2561 '6' => Code::Digit6,
2562 '7' => Code::Digit7,
2563 '8' => Code::Digit8,
2564 '9' => Code::Digit9,
2565 '=' => Code::Equal,
2566 'a' => Code::KeyA,
2567 'b' => Code::KeyB,
2568 'c' => Code::KeyC,
2569 'd' => Code::KeyD,
2570 'e' => Code::KeyE,
2571 'f' => Code::KeyF,
2572 'g' => Code::KeyG,
2573 'h' => Code::KeyH,
2574 'i' => Code::KeyI,
2575 'j' => Code::KeyJ,
2576 'k' => Code::KeyK,
2577 'l' => Code::KeyL,
2578 'm' => Code::KeyM,
2579 'n' => Code::KeyN,
2580 'o' => Code::KeyO,
2581 'p' => Code::KeyP,
2582 'q' => Code::KeyQ,
2583 'r' => Code::KeyR,
2584 's' => Code::KeyS,
2585 't' => Code::KeyT,
2586 'u' => Code::KeyU,
2587 'v' => Code::KeyV,
2588 'w' => Code::KeyW,
2589 'x' => Code::KeyX,
2590 'y' => Code::KeyY,
2591 'z' => Code::KeyZ,
2592 '-' => Code::Minus,
2593 '.' => Code::Period,
2594 '\'' | '"' => Code::Quote,
2595 ';' => Code::Semicolon,
2596 '/' => Code::Slash,
2597 ' ' => Code::Space,
2598 _ => return None,
2599 })
2600}