1use std::array::from_ref;
6use std::cell::{Cell, RefCell};
7use std::f64::consts::PI;
8use std::mem;
9use std::rc::Rc;
10use std::time::{Duration, Instant};
11
12use constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
13use embedder_traits::{
14 Cursor, EditingActionEvent, EmbedderMsg, GamepadEvent as EmbedderGamepadEvent,
15 GamepadSupportedHapticEffects, GamepadUpdateType, ImeEvent, InputEvent, InputEventAndId,
16 InputEventResult, KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction,
17 MouseButtonEvent, MouseLeftViewportEvent, ScrollEvent, TouchEvent as EmbedderTouchEvent,
18 TouchEventType, TouchId, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
19};
20use euclid::{Point2D, Vector2D};
21use ipc_channel::ipc;
22use js::jsapi::JSAutoRealm;
23use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
24use layout_api::{ScrollContainerQueryFlags, node_id_from_scroll_id};
25use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
26use script_bindings::codegen::GenericBindings::EventBinding::EventMethods;
27use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods;
28use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
29use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
30use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
31use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
32use script_bindings::inheritance::Castable;
33use script_bindings::match_domstring_ascii;
34use script_bindings::num::Finite;
35use script_bindings::reflector::DomObject;
36use script_bindings::root::{Dom, DomRoot, DomSlice};
37use script_bindings::script_runtime::CanGc;
38use script_bindings::str::DOMString;
39use script_traits::ConstellationInputEvent;
40use servo_config::pref;
41use style_traits::CSSPixel;
42use xml5ever::{local_name, ns};
43
44use crate::dom::bindings::cell::DomRefCell;
45use crate::dom::bindings::refcounted::Trusted;
46use crate::dom::bindings::root::MutNullableDom;
47use crate::dom::clipboardevent::ClipboardEventType;
48use crate::dom::document::{FireMouseEventType, FocusInitiator};
49use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
50use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
51use crate::dom::gamepad::gamepadevent::GamepadEventType;
52use crate::dom::inputevent::HitTestResult;
53use crate::dom::node::{self, Node, NodeTraits, ShadowIncluding};
54use crate::dom::pointerevent::PointerId;
55use crate::dom::scrolling_box::ScrollingBoxAxis;
56use crate::dom::types::{
57 ClipboardEvent, CompositionEvent, DataTransfer, Element, Event, EventTarget, GlobalScope,
58 HTMLAnchorElement, KeyboardEvent, MouseEvent, PointerEvent, Touch, TouchEvent, TouchList,
59 WheelEvent, Window,
60};
61use crate::drag_data_store::{DragDataStore, Kind, Mode};
62use crate::realms::enter_realm;
63
64#[derive(JSTraceable, MallocSizeOf)]
68#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
69pub(crate) struct DocumentEventHandler {
70 window: Dom<Window>,
72 #[no_trace]
74 #[ignore_malloc_size_of = "CompositorEvent contains data from outside crates"]
75 pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
76 mouse_move_event_index: DomRefCell<Option<usize>>,
78 #[ignore_malloc_size_of = "Defined in std"]
80 #[no_trace]
81 last_click_info: DomRefCell<Option<(Instant, Point2D<f32, CSSPixel>)>>,
82 #[no_trace]
83 last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
84 current_hover_target: MutNullableDom<Element>,
86 most_recently_clicked_element: MutNullableDom<Element>,
88 #[no_trace]
90 most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
91 #[no_trace]
94 current_cursor: Cell<Option<Cursor>>,
95 active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
97 #[no_trace]
99 active_keyboard_modifiers: Cell<Modifiers>,
100}
101
102impl DocumentEventHandler {
103 pub(crate) fn new(window: &Window) -> Self {
104 Self {
105 window: Dom::from_ref(window),
106 pending_input_events: Default::default(),
107 mouse_move_event_index: Default::default(),
108 last_click_info: Default::default(),
109 last_mouse_button_down_point: Default::default(),
110 current_hover_target: Default::default(),
111 most_recently_clicked_element: Default::default(),
112 most_recent_mousemove_point: Default::default(),
113 current_cursor: Default::default(),
114 active_touch_points: Default::default(),
115 active_keyboard_modifiers: Default::default(),
116 }
117 }
118
119 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
121 let mut pending_compositor_events = self.pending_input_events.borrow_mut();
122 if matches!(event.event.event, InputEvent::MouseMove(..)) {
123 if let Some(mouse_move_event) = self
125 .mouse_move_event_index
126 .borrow()
127 .and_then(|index| pending_compositor_events.get_mut(index))
128 {
129 *mouse_move_event = event;
130 return;
131 }
132
133 *self.mouse_move_event_index.borrow_mut() = Some(pending_compositor_events.len());
134 }
135
136 pending_compositor_events.push(event);
137 }
138
139 pub(crate) fn has_pending_input_events(&self) -> bool {
142 !self.pending_input_events.borrow().is_empty()
143 }
144
145 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
146 #[cfg(target_os = "macos")]
147 {
148 self.active_keyboard_modifiers
149 .get()
150 .contains(Modifiers::META)
151 }
152 #[cfg(not(target_os = "macos"))]
153 {
154 self.active_keyboard_modifiers
155 .get()
156 .contains(Modifiers::CONTROL)
157 }
158 }
159
160 pub(crate) fn handle_pending_input_events(&self, can_gc: CanGc) {
161 let _realm = enter_realm(&*self.window);
162
163 *self.mouse_move_event_index.borrow_mut() = None;
165 let pending_input_events = mem::take(&mut *self.pending_input_events.borrow_mut());
166
167 for event in pending_input_events {
168 self.active_keyboard_modifiers
169 .set(event.active_keyboard_modifiers);
170
171 let result = match event.event.event.clone() {
177 InputEvent::MouseButton(mouse_button_event) => {
178 self.handle_native_mouse_button_event(mouse_button_event, &event, can_gc);
179 InputEventResult::default()
180 },
181 InputEvent::MouseMove(_) => {
182 self.handle_native_mouse_move_event(&event, can_gc);
183 InputEventResult::default()
184 },
185 InputEvent::MouseLeftViewport(mouse_leave_event) => {
186 self.handle_mouse_left_viewport_event(&event, &mouse_leave_event, can_gc);
187 InputEventResult::default()
188 },
189 InputEvent::Touch(touch_event) => {
190 self.handle_touch_event(touch_event, &event, can_gc)
191 },
192 InputEvent::Wheel(wheel_event) => {
193 self.handle_wheel_event(wheel_event, &event, can_gc)
194 },
195 InputEvent::Keyboard(keyboard_event) => {
196 self.handle_keyboard_event(keyboard_event, can_gc)
197 },
198 InputEvent::Ime(ime_event) => self.handle_ime_event(ime_event, can_gc),
199 InputEvent::Gamepad(gamepad_event) => {
200 self.handle_gamepad_event(gamepad_event);
201 InputEventResult::default()
202 },
203 InputEvent::EditingAction(editing_action_event) => {
204 self.handle_editing_action(None, editing_action_event, can_gc)
205 },
206 InputEvent::Scroll(scroll_event) => {
207 self.handle_embedder_scroll_event(scroll_event);
208 InputEventResult::default()
209 },
210 };
211
212 self.notify_embedder_that_event_was_handled(event.event, result);
213 }
214 }
215
216 fn notify_embedder_that_event_was_handled(
217 &self,
218 event: InputEventAndId,
219 result: InputEventResult,
220 ) {
221 let id = event.id;
224 let trusted_window = Trusted::new(&*self.window);
225 self.window
226 .as_global_scope()
227 .task_manager()
228 .dom_manipulation_task_source()
229 .queue(task!(notify_webdriver_input_event_completed: move || {
230 let window = trusted_window.root();
231 window.send_to_embedder(
232 EmbedderMsg::InputEventHandled(window.webview_id(), id, result));
233 }));
234 }
235
236 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
237 if cursor == self.current_cursor.get() {
238 return;
239 }
240 self.current_cursor.set(cursor);
241 self.window.send_to_embedder(EmbedderMsg::SetCursor(
242 self.window.webview_id(),
243 cursor.unwrap_or_default(),
244 ));
245 }
246
247 fn handle_mouse_left_viewport_event(
248 &self,
249 input_event: &ConstellationInputEvent,
250 mouse_leave_event: &MouseLeftViewportEvent,
251 can_gc: CanGc,
252 ) {
253 if let Some(current_hover_target) = self.current_hover_target.get() {
254 let current_hover_target = current_hover_target.upcast::<Node>();
255 for element in current_hover_target
256 .inclusive_ancestors(ShadowIncluding::No)
257 .filter_map(DomRoot::downcast::<Element>)
258 {
259 element.set_hover_state(false);
260 element.set_active_state(false);
261 }
262
263 if let Some(hit_test_result) = self
264 .most_recent_mousemove_point
265 .get()
266 .and_then(|point| self.window.hit_test_from_point_in_viewport(point))
267 {
268 MouseEvent::new_simple(
269 &self.window,
270 FireMouseEventType::Out,
271 EventBubbles::Bubbles,
272 EventCancelable::Cancelable,
273 &hit_test_result,
274 input_event,
275 can_gc,
276 )
277 .upcast::<Event>()
278 .fire(current_hover_target.upcast(), can_gc);
279 self.handle_mouse_enter_leave_event(
280 DomRoot::from_ref(current_hover_target),
281 None,
282 FireMouseEventType::Leave,
283 &hit_test_result,
284 input_event,
285 can_gc,
286 );
287 }
288 }
289
290 if !mouse_leave_event.focus_moving_to_another_iframe {
295 self.window
299 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
300 self.set_cursor(None);
301 } else {
302 self.current_cursor.set(None);
303 }
304
305 self.current_hover_target.set(None);
306 self.most_recent_mousemove_point.set(None);
307 }
308
309 fn handle_mouse_enter_leave_event(
310 &self,
311 event_target: DomRoot<Node>,
312 related_target: Option<DomRoot<Node>>,
313 event_type: FireMouseEventType,
314 hit_test_result: &HitTestResult,
315 input_event: &ConstellationInputEvent,
316 can_gc: CanGc,
317 ) {
318 assert!(matches!(
319 event_type,
320 FireMouseEventType::Enter | FireMouseEventType::Leave
321 ));
322
323 let common_ancestor = match related_target.as_ref() {
324 Some(related_target) => event_target
325 .common_ancestor(related_target, ShadowIncluding::No)
326 .unwrap_or_else(|| DomRoot::from_ref(&*event_target)),
327 None => DomRoot::from_ref(&*event_target),
328 };
329
330 let mut targets = vec![];
333 let mut current = Some(event_target);
334 while let Some(node) = current {
335 if node == common_ancestor {
336 break;
337 }
338 current = node.GetParentNode();
339 targets.push(node);
340 }
341
342 if event_type == FireMouseEventType::Enter {
345 targets = targets.into_iter().rev().collect();
346 }
347
348 for target in targets {
349 MouseEvent::new_simple(
350 &self.window,
351 event_type,
352 EventBubbles::DoesNotBubble,
353 EventCancelable::NotCancelable,
354 hit_test_result,
355 input_event,
356 can_gc,
357 )
358 .upcast::<Event>()
359 .fire(target.upcast(), can_gc);
360 }
361 }
362
363 fn handle_native_mouse_move_event(&self, input_event: &ConstellationInputEvent, can_gc: CanGc) {
365 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
367 return;
368 };
369
370 self.set_cursor(Some(hit_test_result.cursor));
372
373 let Some(new_target) = hit_test_result
374 .node
375 .inclusive_ancestors(ShadowIncluding::No)
376 .find_map(DomRoot::downcast::<Element>)
377 else {
378 return;
379 };
380
381 let target_has_changed = self
382 .current_hover_target
383 .get()
384 .is_none_or(|old_target| old_target != new_target);
385
386 if target_has_changed {
389 if let Some(old_target) = self.current_hover_target.get() {
391 let old_target_is_ancestor_of_new_target = old_target
392 .upcast::<Node>()
393 .is_ancestor_of(new_target.upcast::<Node>());
394
395 if !old_target_is_ancestor_of_new_target {
398 for element in old_target
399 .upcast::<Node>()
400 .inclusive_ancestors(ShadowIncluding::No)
401 .filter_map(DomRoot::downcast::<Element>)
402 {
403 element.set_hover_state(false);
404 element.set_active_state(false);
405 }
406 }
407
408 MouseEvent::new_simple(
409 &self.window,
410 FireMouseEventType::Out,
411 EventBubbles::Bubbles,
412 EventCancelable::Cancelable,
413 &hit_test_result,
414 input_event,
415 can_gc,
416 )
417 .upcast::<Event>()
418 .fire(old_target.upcast(), can_gc);
419
420 if !old_target_is_ancestor_of_new_target {
421 let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
422 let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
423 self.handle_mouse_enter_leave_event(
424 event_target,
425 moving_into,
426 FireMouseEventType::Leave,
427 &hit_test_result,
428 input_event,
429 can_gc,
430 );
431 }
432 }
433
434 for element in new_target
436 .upcast::<Node>()
437 .inclusive_ancestors(ShadowIncluding::No)
438 .filter_map(DomRoot::downcast::<Element>)
439 {
440 element.set_hover_state(true);
441 }
442
443 MouseEvent::new_simple(
444 &self.window,
445 FireMouseEventType::Over,
446 EventBubbles::Bubbles,
447 EventCancelable::Cancelable,
448 &hit_test_result,
449 input_event,
450 can_gc,
451 )
452 .upcast::<Event>()
453 .fire(new_target.upcast(), can_gc);
454
455 let moving_from = self
456 .current_hover_target
457 .get()
458 .map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
459 let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
460 self.handle_mouse_enter_leave_event(
461 event_target,
462 moving_from,
463 FireMouseEventType::Enter,
464 &hit_test_result,
465 input_event,
466 can_gc,
467 );
468 }
469
470 MouseEvent::new_simple(
473 &self.window,
474 FireMouseEventType::Move,
475 EventBubbles::Bubbles,
476 EventCancelable::Cancelable,
477 &hit_test_result,
478 input_event,
479 can_gc,
480 )
481 .upcast::<Event>()
482 .fire(new_target.upcast(), can_gc);
483
484 self.update_current_hover_target_and_status(Some(new_target));
485 self.most_recent_mousemove_point
486 .set(Some(hit_test_result.point_in_frame));
487 }
488
489 fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
490 let current_hover_target = self.current_hover_target.get();
491 if current_hover_target == new_hover_target {
492 return;
493 }
494
495 let previous_hover_target = self.current_hover_target.get();
496 self.current_hover_target.set(new_hover_target.as_deref());
497
498 if let Some(target) = self.current_hover_target.get() {
501 if let Some(anchor) = target
502 .upcast::<Node>()
503 .inclusive_ancestors(ShadowIncluding::No)
504 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
505 {
506 let status = anchor
507 .upcast::<Element>()
508 .get_attribute(&ns!(), &local_name!("href"))
509 .and_then(|href| {
510 let value = href.value();
511 let url = self.window.get_url();
512 url.join(&value).map(|url| url.to_string()).ok()
513 });
514 self.window
515 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
516 return;
517 }
518 }
519
520 if previous_hover_target.is_none_or(|previous_hover_target| {
525 previous_hover_target
526 .upcast::<Node>()
527 .inclusive_ancestors(ShadowIncluding::No)
528 .any(|node| node.is::<HTMLAnchorElement>())
529 }) {
530 self.window
531 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
532 }
533 }
534
535 pub(crate) fn handle_refresh_cursor(&self) {
536 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
537 return;
538 };
539
540 let Some(hit_test_result) = self
541 .window
542 .hit_test_from_point_in_viewport(most_recent_mousemove_point)
543 else {
544 return;
545 };
546
547 self.set_cursor(Some(hit_test_result.cursor));
548 }
549
550 fn handle_native_mouse_button_event(
553 &self,
554 event: MouseButtonEvent,
555 input_event: &ConstellationInputEvent,
556 can_gc: CanGc,
557 ) {
558 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
560 return;
561 };
562
563 debug!(
564 "{:?}: at {:?}",
565 event.action, hit_test_result.point_in_frame
566 );
567
568 let Some(element) = hit_test_result
569 .node
570 .inclusive_ancestors(ShadowIncluding::Yes)
571 .find_map(DomRoot::downcast::<Element>)
572 else {
573 return;
574 };
575
576 let node = element.upcast::<Node>();
577 debug!("{:?} on {:?}", event.action, node.debug_str());
578
579 if element.is_actually_disabled() {
583 return;
584 }
585
586 let mouse_event_type_string = match event.action {
587 embedder_traits::MouseButtonAction::Up => "mouseup",
588 embedder_traits::MouseButtonAction::Down => "mousedown",
589 };
590 let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_mouse_event(
591 mouse_event_type_string,
592 event,
593 input_event.pressed_mouse_buttons,
594 &self.window,
595 &hit_test_result,
596 input_event.active_keyboard_modifiers,
597 can_gc,
598 ));
599
600 let activatable = element.as_maybe_activatable();
601 match event.action {
602 MouseButtonAction::Down => {
603 self.last_mouse_button_down_point
604 .set(Some(hit_test_result.point_in_frame));
605
606 if let Some(a) = activatable {
607 a.enter_formal_activation_state();
608 }
609
610 let target_el = element.find_focusable_shadow_host_if_necessary();
617
618 let document = self.window.Document();
619 document.begin_focus_transaction();
620
621 document.request_focus(None, FocusInitiator::Local, can_gc);
623 document.request_focus(target_el.as_deref(), FocusInitiator::Local, can_gc);
624
625 let result = dom_event.dispatch(node.upcast(), false, can_gc);
627
628 if result && document.has_focus_transaction() {
631 document.commit_focus_transaction(FocusInitiator::Local, can_gc);
632 }
633
634 if let MouseButton::Right = event.button {
637 self.maybe_show_context_menu(
638 node.upcast(),
639 &hit_test_result,
640 input_event,
641 can_gc,
642 );
643 }
644 },
645 MouseButtonAction::Up => {
647 if let Some(a) = activatable {
648 a.exit_formal_activation_state();
649 }
650
651 dom_event.dispatch(node.upcast(), false, can_gc);
655
656 self.maybe_trigger_click_for_mouse_button_down_event(
657 event,
658 input_event,
659 &hit_test_result,
660 &element,
661 can_gc,
662 );
663 },
664 }
665 }
666
667 fn maybe_trigger_click_for_mouse_button_down_event(
669 &self,
670 event: MouseButtonEvent,
671 input_event: &ConstellationInputEvent,
672 hit_test_result: &HitTestResult,
673 element: &Element,
674 can_gc: CanGc,
675 ) {
676 if event.button != MouseButton::Left {
677 return;
678 }
679 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
680 return;
681 };
682
683 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
684 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
685 if distance > maximum_click_distance {
686 return;
687 }
688
689 self.most_recently_clicked_element.set(Some(element));
694
695 element.set_click_in_progress(true);
696 let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_mouse_event(
697 "click",
698 event,
699 input_event.pressed_mouse_buttons,
700 &self.window,
701 hit_test_result,
702 input_event.active_keyboard_modifiers,
703 can_gc,
704 ));
705 let node = element.upcast::<Node>();
706 dom_event.dispatch(node.upcast(), false, can_gc);
707 element.set_click_in_progress(false);
708
709 self.maybe_fire_dblclick(node, hit_test_result, input_event, can_gc);
710 }
711
712 fn maybe_show_context_menu(
714 &self,
715 target: &EventTarget,
716 hit_test_result: &HitTestResult,
717 input_event: &ConstellationInputEvent,
718 can_gc: CanGc,
719 ) {
720 let menu_event = PointerEvent::new(
722 &self.window, DOMString::from("contextmenu"), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
729 hit_test_result.point_in_frame.to_i32(),
730 hit_test_result
731 .point_relative_to_initial_containing_block
732 .to_i32(),
733 input_event.active_keyboard_modifiers,
734 2i16, input_event.pressed_mouse_buttons,
736 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,
753 );
754
755 let result = menu_event.upcast::<Event>().fire(target, can_gc);
757
758 if result {
760 self.window
761 .Document()
762 .embedder_controls()
763 .show_context_menu(hit_test_result);
764 };
765 }
766
767 fn maybe_fire_dblclick(
768 &self,
769 target: &Node,
770 hit_test_result: &HitTestResult,
771 input_event: &ConstellationInputEvent,
772 can_gc: CanGc,
773 ) {
774 let now = Instant::now();
776 let point_in_frame = hit_test_result.point_in_frame;
777 let opt = self.last_click_info.borrow_mut().take();
778
779 if let Some((last_time, last_pos)) = opt {
780 let double_click_timeout =
781 Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
782 let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
783
784 let line = point_in_frame - last_pos;
786 let dist = (line.dot(line) as f64).sqrt();
787
788 if now.duration_since(last_time) < double_click_timeout &&
789 dist < double_click_distance_threshold as f64
790 {
791 let click_count = 2;
793
794 let event = MouseEvent::new(
795 &self.window,
796 DOMString::from("dblclick"),
797 EventBubbles::Bubbles,
798 EventCancelable::Cancelable,
799 Some(&self.window),
800 click_count,
801 point_in_frame.to_i32(),
802 point_in_frame.to_i32(),
803 hit_test_result
804 .point_relative_to_initial_containing_block
805 .to_i32(),
806 input_event.active_keyboard_modifiers,
807 0i16,
808 input_event.pressed_mouse_buttons,
809 None,
810 None,
811 can_gc,
812 );
813 event.upcast::<Event>().fire(target.upcast(), can_gc);
814
815 return;
818 }
819 }
820
821 *self.last_click_info.borrow_mut() = Some((now, point_in_frame));
823 }
824
825 fn handle_touch_event(
826 &self,
827 event: EmbedderTouchEvent,
828 input_event: &ConstellationInputEvent,
829 can_gc: CanGc,
830 ) -> InputEventResult {
831 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
833 self.update_active_touch_points_when_early_return(event);
834 return Default::default();
835 };
836
837 let TouchId(identifier) = event.id;
838 let event_name = match event.event_type {
839 TouchEventType::Down => "touchstart",
840 TouchEventType::Move => "touchmove",
841 TouchEventType::Up => "touchend",
842 TouchEventType::Cancel => "touchcancel",
843 };
844
845 let Some(el) = hit_test_result
846 .node
847 .inclusive_ancestors(ShadowIncluding::No)
848 .find_map(DomRoot::downcast::<Element>)
849 else {
850 self.update_active_touch_points_when_early_return(event);
851 return Default::default();
852 };
853
854 let target = DomRoot::upcast::<EventTarget>(el);
855 let window = &*self.window;
856
857 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
858 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
859 let page_x =
860 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
861 let page_y =
862 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
863
864 let touch = Touch::new(
865 window, identifier, &target, client_x,
866 client_y, client_x, client_y, page_x, page_y, can_gc,
868 );
869
870 match event.event_type {
871 TouchEventType::Down => {
872 self.active_touch_points
874 .borrow_mut()
875 .push(Dom::from_ref(&*touch));
876 },
877 TouchEventType::Move => {
878 let mut active_touch_points = self.active_touch_points.borrow_mut();
880 match active_touch_points
881 .iter_mut()
882 .find(|t| t.Identifier() == identifier)
883 {
884 Some(t) => *t = Dom::from_ref(&*touch),
885 None => warn!("Got a touchmove event for a non-active touch point"),
886 }
887 },
888 TouchEventType::Up | TouchEventType::Cancel => {
889 let mut active_touch_points = self.active_touch_points.borrow_mut();
891 match active_touch_points
892 .iter()
893 .position(|t| t.Identifier() == identifier)
894 {
895 Some(i) => {
896 active_touch_points.swap_remove(i);
897 },
898 None => warn!("Got a touchend event for a non-active touch point"),
899 }
900 },
901 }
902
903 rooted_vec!(let mut target_touches);
904 let touches = {
905 let touches = self.active_touch_points.borrow();
906 target_touches.extend(touches.iter().filter(|t| t.Target() == target).cloned());
907 TouchList::new(window, touches.r(), can_gc)
908 };
909
910 let touch_event = TouchEvent::new(
911 window,
912 DOMString::from(event_name),
913 EventBubbles::Bubbles,
914 EventCancelable::from(event.is_cancelable()),
915 EventComposed::Composed,
916 Some(window),
917 0i32,
918 &touches,
919 &TouchList::new(window, from_ref(&&*touch), can_gc),
920 &TouchList::new(window, target_touches.r(), can_gc),
921 false,
923 false,
924 false,
925 false,
926 can_gc,
927 );
928
929 let event = touch_event.upcast::<Event>();
930 event.fire(&target, can_gc);
931 event.flags().into()
932 }
933
934 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
936 match event.event_type {
937 TouchEventType::Down => {
938 },
942 TouchEventType::Move => {
943 },
946 TouchEventType::Up | TouchEventType::Cancel => {
947 let mut active_touch_points = self.active_touch_points.borrow_mut();
949 match active_touch_points
950 .iter()
951 .position(|t| t.Identifier() == event.id.0)
952 {
953 Some(i) => {
954 active_touch_points.swap_remove(i);
955 },
956 None => warn!("Got a touchend event for a non-active touch point"),
957 }
958 },
959 }
960 }
961
962 fn handle_keyboard_event(
964 &self,
965 keyboard_event: EmbedderKeyboardEvent,
966 can_gc: CanGc,
967 ) -> InputEventResult {
968 let document = self.window.Document();
969 let focused = document.get_focused_element();
970 let body = document.GetBody();
971
972 let target = match (&focused, &body) {
973 (Some(focused), _) => focused.upcast(),
974 (&None, Some(body)) => body.upcast(),
975 (&None, &None) => self.window.upcast(),
976 };
977
978 let keyevent = KeyboardEvent::new(
979 &self.window,
980 DOMString::from(keyboard_event.event.state.event_type()),
981 true,
982 true,
983 Some(&self.window),
984 0,
985 keyboard_event.event.key.clone(),
986 DOMString::from(keyboard_event.event.code.to_string()),
987 keyboard_event.event.location as u32,
988 keyboard_event.event.repeat,
989 keyboard_event.event.is_composing,
990 keyboard_event.event.modifiers,
991 0,
992 keyboard_event.event.key.legacy_keycode(),
993 can_gc,
994 );
995
996 let event = keyevent.upcast::<Event>();
997 event.fire(target, can_gc);
998
999 let mut flags = event.flags();
1000 if flags.contains(EventFlags::Canceled) {
1001 return flags.into();
1002 }
1003
1004 let is_character_value_key = matches!(
1010 keyboard_event.event.key,
1011 Key::Character(_) | Key::Named(NamedKey::Enter)
1012 );
1013 if keyboard_event.event.state == KeyState::Down &&
1014 is_character_value_key &&
1015 !keyboard_event.event.is_composing
1016 {
1017 let keypress_event = KeyboardEvent::new(
1019 &self.window,
1020 DOMString::from("keypress"),
1021 true,
1022 true,
1023 Some(&self.window),
1024 0,
1025 keyboard_event.event.key.clone(),
1026 DOMString::from(keyboard_event.event.code.to_string()),
1027 keyboard_event.event.location as u32,
1028 keyboard_event.event.repeat,
1029 keyboard_event.event.is_composing,
1030 keyboard_event.event.modifiers,
1031 keyboard_event.event.key.legacy_charcode(),
1032 0,
1033 can_gc,
1034 );
1035 let event = keypress_event.upcast::<Event>();
1036 event.fire(target, can_gc);
1037 flags = event.flags();
1038 }
1039
1040 if flags.contains(EventFlags::Canceled) {
1041 return flags.into();
1042 }
1043
1044 if (keyboard_event.event.key == Key::Named(NamedKey::Enter) ||
1050 keyboard_event.event.code == Code::Space) &&
1051 keyboard_event.event.state == KeyState::Up
1052 {
1053 if let Some(elem) = target.downcast::<Element>() {
1054 elem.upcast::<Node>()
1055 .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
1056 }
1057 }
1058
1059 flags.into()
1060 }
1061
1062 fn handle_ime_event(&self, event: ImeEvent, can_gc: CanGc) -> InputEventResult {
1063 let document = self.window.Document();
1064 let composition_event = match event {
1065 ImeEvent::Dismissed => {
1066 document.request_focus(
1067 document.GetBody().as_ref().map(|e| e.upcast()),
1068 FocusInitiator::Local,
1069 can_gc,
1070 );
1071 return Default::default();
1072 },
1073 ImeEvent::Composition(composition_event) => composition_event,
1074 };
1075
1076 let focused = document.get_focused_element();
1081 let target = if let Some(elem) = &focused {
1082 elem.upcast()
1083 } else {
1084 return Default::default();
1086 };
1087
1088 let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1089 let event = CompositionEvent::new(
1090 &self.window,
1091 DOMString::from(composition_event.state.event_type()),
1092 true,
1093 cancelable,
1094 Some(&self.window),
1095 0,
1096 DOMString::from(composition_event.data),
1097 can_gc,
1098 );
1099
1100 let event = event.upcast::<Event>();
1101 event.fire(target, can_gc);
1102 event.flags().into()
1103 }
1104
1105 fn handle_wheel_event(
1106 &self,
1107 event: EmbedderWheelEvent,
1108 input_event: &ConstellationInputEvent,
1109 can_gc: CanGc,
1110 ) -> InputEventResult {
1111 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1113 return Default::default();
1114 };
1115
1116 let Some(el) = hit_test_result
1117 .node
1118 .inclusive_ancestors(ShadowIncluding::No)
1119 .find_map(DomRoot::downcast::<Element>)
1120 else {
1121 return Default::default();
1122 };
1123
1124 let node = el.upcast::<Node>();
1125 let wheel_event_type_string = "wheel".to_owned();
1126 debug!(
1127 "{}: on {:?} at {:?}",
1128 wheel_event_type_string,
1129 node.debug_str(),
1130 hit_test_result.point_in_frame
1131 );
1132
1133 let dom_event = WheelEvent::new(
1135 &self.window,
1136 DOMString::from(wheel_event_type_string),
1137 EventBubbles::Bubbles,
1138 EventCancelable::Cancelable,
1139 Some(&self.window),
1140 0i32,
1141 hit_test_result.point_in_frame.to_i32(),
1142 hit_test_result.point_in_frame.to_i32(),
1143 hit_test_result
1144 .point_relative_to_initial_containing_block
1145 .to_i32(),
1146 input_event.active_keyboard_modifiers,
1147 0i16,
1148 input_event.pressed_mouse_buttons,
1149 None,
1150 None,
1151 Finite::wrap(-event.delta.x),
1156 Finite::wrap(-event.delta.y),
1157 Finite::wrap(-event.delta.z),
1158 event.delta.mode as u32,
1159 can_gc,
1160 );
1161
1162 let dom_event = dom_event.upcast::<Event>();
1163 dom_event.set_trusted(true);
1164 dom_event.fire(node.upcast(), can_gc);
1165
1166 dom_event.flags().into()
1167 }
1168
1169 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1170 match gamepad_event {
1171 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1172 self.handle_gamepad_connect(
1173 index.0,
1174 name,
1175 bounds.axis_bounds,
1176 bounds.button_bounds,
1177 supported_haptic_effects,
1178 );
1179 },
1180 EmbedderGamepadEvent::Disconnected(index) => {
1181 self.handle_gamepad_disconnect(index.0);
1182 },
1183 EmbedderGamepadEvent::Updated(index, update_type) => {
1184 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1185 },
1186 };
1187 }
1188
1189 fn handle_gamepad_connect(
1191 &self,
1192 _index: usize,
1196 name: String,
1197 axis_bounds: (f64, f64),
1198 button_bounds: (f64, f64),
1199 supported_haptic_effects: GamepadSupportedHapticEffects,
1200 ) {
1201 let trusted_window = Trusted::new(&*self.window);
1204 self.window
1205 .upcast::<GlobalScope>()
1206 .task_manager()
1207 .gamepad_task_source()
1208 .queue(task!(gamepad_connected: move || {
1209 let window = trusted_window.root();
1210
1211 let navigator = window.Navigator();
1212 let selected_index = navigator.select_gamepad_index();
1213 let gamepad = Gamepad::new(
1214 &window,
1215 selected_index,
1216 name,
1217 "standard".into(),
1218 axis_bounds,
1219 button_bounds,
1220 supported_haptic_effects,
1221 false,
1222 CanGc::note(),
1223 );
1224 navigator.set_gamepad(selected_index as usize, &gamepad, CanGc::note());
1225 }));
1226 }
1227
1228 fn handle_gamepad_disconnect(&self, index: usize) {
1230 let trusted_window = Trusted::new(&*self.window);
1231 self.window
1232 .upcast::<GlobalScope>()
1233 .task_manager()
1234 .gamepad_task_source()
1235 .queue(task!(gamepad_disconnected: move || {
1236 let window = trusted_window.root();
1237 let navigator = window.Navigator();
1238 if let Some(gamepad) = navigator.get_gamepad(index) {
1239 if window.Document().is_fully_active() {
1240 gamepad.update_connected(false, gamepad.exposed(), CanGc::note());
1241 navigator.remove_gamepad(index);
1242 }
1243 }
1244 }));
1245 }
1246
1247 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1249 let trusted_window = Trusted::new(&*self.window);
1250
1251 self.window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1253 task!(update_gamepad_state: move || {
1254 let window = trusted_window.root();
1255 let navigator = window.Navigator();
1256 if let Some(gamepad) = navigator.get_gamepad(index) {
1257 let current_time = window.Performance().Now();
1258 gamepad.update_timestamp(*current_time);
1259 match update_type {
1260 GamepadUpdateType::Axis(index, value) => {
1261 gamepad.map_and_normalize_axes(index, value);
1262 },
1263 GamepadUpdateType::Button(index, value) => {
1264 gamepad.map_and_normalize_buttons(index, value);
1265 }
1266 };
1267 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1268 navigator.set_has_gamepad_gesture(true);
1269 navigator.GetGamepads()
1270 .iter()
1271 .filter_map(|g| g.as_ref())
1272 .for_each(|gamepad| {
1273 gamepad.set_exposed(true);
1274 gamepad.update_timestamp(*current_time);
1275 let new_gamepad = Trusted::new(&**gamepad);
1276 if window.Document().is_fully_active() {
1277 window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1278 task!(update_gamepad_connect: move || {
1279 let gamepad = new_gamepad.root();
1280 gamepad.notify_event(GamepadEventType::Connected, CanGc::note());
1281 })
1282 );
1283 }
1284 });
1285 }
1286 }
1287 })
1288 );
1289 }
1290
1291 pub(crate) fn handle_editing_action(
1293 &self,
1294 element: Option<DomRoot<Element>>,
1295 action: EditingActionEvent,
1296 can_gc: CanGc,
1297 ) -> InputEventResult {
1298 let clipboard_event_type = match action {
1299 EditingActionEvent::Copy => ClipboardEventType::Copy,
1300 EditingActionEvent::Cut => ClipboardEventType::Cut,
1301 EditingActionEvent::Paste => ClipboardEventType::Paste,
1302 };
1303
1304 let script_triggered = false;
1306
1307 let script_may_access_clipboard = false;
1311
1312 if script_triggered && !script_may_access_clipboard {
1314 return InputEventResult::empty();
1315 }
1316
1317 let clipboard_event =
1319 self.fire_clipboard_event(element.clone(), clipboard_event_type, can_gc);
1320
1321 let event = clipboard_event.upcast::<Event>();
1324 if !event.IsTrusted() {
1325 return event.flags().into();
1326 }
1327
1328 if event.DefaultPrevented() {
1330 let event_type = event.Type();
1331 match_domstring_ascii!(event_type,
1332
1333 "copy" => {
1334 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1337 let drag_data_store =
1338 clipboard_data.data_store().expect("This shouldn't fail");
1339 self.write_content_to_the_clipboard(&drag_data_store);
1340 }
1341 },
1342 "cut" => {
1343 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1346 let drag_data_store =
1347 clipboard_data.data_store().expect("This shouldn't fail");
1348 self.write_content_to_the_clipboard(&drag_data_store);
1349 }
1350
1351 self.fire_clipboard_event(element, ClipboardEventType::Change, can_gc);
1353 },
1354 "paste" => (),
1358 _ => (),
1359 )
1360 }
1361
1362 event.flags().into()
1365 }
1366
1367 pub(crate) fn fire_clipboard_event(
1369 &self,
1370 target: Option<DomRoot<Element>>,
1371 clipboard_event_type: ClipboardEventType,
1372 can_gc: CanGc,
1373 ) -> DomRoot<ClipboardEvent> {
1374 let clipboard_event = ClipboardEvent::new(
1375 &self.window,
1376 None,
1377 DOMString::from(clipboard_event_type.as_str()),
1378 EventBubbles::Bubbles,
1379 EventCancelable::Cancelable,
1380 None,
1381 can_gc,
1382 );
1383
1384 let mut drag_data_store = DragDataStore::new();
1387
1388 let trusted = true;
1392
1393 let document = self.window.Document();
1395 let target = target.or(document.get_focused_element());
1396 let target = target
1397 .map(|target| DomRoot::from_ref(target.upcast()))
1398 .or_else(|| {
1399 document
1400 .GetBody()
1401 .map(|body| DomRoot::from_ref(body.upcast()))
1402 })
1403 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast()));
1404
1405 match clipboard_event_type {
1408 ClipboardEventType::Copy | ClipboardEventType::Cut => {
1409 drag_data_store.set_mode(Mode::ReadWrite);
1411 },
1412 ClipboardEventType::Paste => {
1413 let (sender, receiver) = ipc::channel().unwrap();
1414 self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
1415 self.window.webview_id(),
1416 sender,
1417 ));
1418 let text_contents = receiver
1419 .recv()
1420 .map(Result::unwrap_or_default)
1421 .unwrap_or_default();
1422
1423 drag_data_store.set_mode(Mode::ReadOnly);
1425 if trusted {
1427 let data = DOMString::from(text_contents.to_string());
1431 let type_ = DOMString::from("text/plain");
1432 let _ = drag_data_store.add(Kind::Text { data, type_ });
1433
1434 }
1440 },
1441 ClipboardEventType::Change => (),
1442 }
1443
1444 let clipboard_event_data = DataTransfer::new(
1446 &self.window,
1447 Rc::new(RefCell::new(Some(drag_data_store))),
1448 can_gc,
1449 );
1450
1451 clipboard_event.set_clipboard_data(Some(&clipboard_event_data));
1453
1454 let event = clipboard_event.upcast::<Event>();
1456 event.set_trusted(trusted);
1457
1458 event.set_composed(true);
1460
1461 event.dispatch(&target, false, can_gc);
1463
1464 DomRoot::from(clipboard_event)
1465 }
1466
1467 fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
1469 if drag_data_store.list_len() > 0 {
1471 self.window
1473 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1474 for item in drag_data_store.iter_item_list() {
1476 match item {
1477 Kind::Text { data, .. } => {
1478 self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
1482 self.window.webview_id(),
1483 data.to_string(),
1484 ));
1485 },
1486 Kind::File { .. } => {
1487 },
1491 }
1492 }
1493 } else {
1494 if drag_data_store.clear_was_called {
1496 self.window
1498 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1499 }
1502 }
1503 }
1504
1505 #[expect(unsafe_code)]
1508 fn handle_embedder_scroll_event(&self, event: ScrollEvent) {
1509 let document = self.window.Document();
1511 if event.external_id.is_root() {
1512 document.handle_viewport_scroll_event();
1513 } else {
1514 let Some(node_id) = node_id_from_scroll_id(event.external_id.0 as usize) else {
1516 return;
1517 };
1518 let node = unsafe {
1519 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
1520 };
1521 let Some(element) = node
1522 .inclusive_ancestors(ShadowIncluding::No)
1523 .find_map(DomRoot::downcast::<Element>)
1524 else {
1525 return;
1526 };
1527
1528 document.handle_element_scroll_event(&element);
1529 }
1530 }
1531
1532 pub(crate) fn run_default_keyboard_event_handler(&self, event: &KeyboardEvent) {
1533 if event.upcast::<Event>().type_() != atom!("keydown") {
1534 return;
1535 }
1536 if !event.modifiers().is_empty() {
1537 return;
1538 }
1539 let scroll = match event.key() {
1540 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
1541 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
1542 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
1543 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
1544 Key::Named(NamedKey::End) => KeyboardScroll::End,
1545 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
1546 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
1547 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
1548 _ => return,
1549 };
1550 self.do_keyboard_scroll(scroll);
1551 }
1552
1553 pub(crate) fn do_keyboard_scroll(&self, scroll: KeyboardScroll) {
1554 let scroll_axis = match scroll {
1555 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
1556 _ => ScrollingBoxAxis::Y,
1557 };
1558
1559 let document = self.window.Document();
1560 let mut scrolling_box = document
1561 .get_focused_element()
1562 .or(self.most_recently_clicked_element.get())
1563 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
1564 .unwrap_or_else(|| {
1565 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
1566 });
1567
1568 while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
1569 if scrolling_box.is_viewport() {
1571 break;
1572 }
1573 let parent = scrolling_box.parent().unwrap_or_else(|| {
1574 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
1575 });
1576 scrolling_box = parent;
1577 }
1578
1579 let calculate_current_scroll_offset_and_delta = || {
1580 const LINE_HEIGHT: f32 = 76.0;
1581 const LINE_WIDTH: f32 = 76.0;
1582
1583 let current_scroll_offset = scrolling_box.scroll_position();
1584 (
1585 current_scroll_offset,
1586 match scroll {
1587 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
1588 KeyboardScroll::End => Vector2D::new(
1589 0.0,
1590 -current_scroll_offset.y + scrolling_box.content_size().height -
1591 scrolling_box.size().height,
1592 ),
1593 KeyboardScroll::PageDown => {
1594 Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
1595 },
1596 KeyboardScroll::PageUp => {
1597 Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
1598 },
1599 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
1600 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
1601 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
1602 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
1603 },
1604 )
1605 };
1606
1607 let parent_pipeline = self.window.parent_info();
1611 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
1612 let (_, delta) = calculate_current_scroll_offset_and_delta();
1613 self.window
1614 .compositor_api()
1615 .scroll_viewport_by_delta(self.window.webview_id(), delta);
1616 }
1617
1618 if !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
1621 assert!(scrolling_box.is_viewport());
1622
1623 let window_proxy = document.window().window_proxy();
1624 if let Some(iframe) = window_proxy.frame_element() {
1625 let cx = GlobalScope::get_cx();
1628 let iframe_window = iframe.owner_window();
1629 let _ac = JSAutoRealm::new(*cx, iframe_window.reflector().get_jsobject().get());
1630 iframe_window
1631 .Document()
1632 .event_handler()
1633 .do_keyboard_scroll(scroll);
1634 } else if let Some(parent_pipeline) = parent_pipeline {
1635 document.window().send_to_constellation(
1639 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
1640 );
1641 };
1642 return;
1643 }
1644
1645 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
1646 scrolling_box.scroll_to(delta + current_scroll_offset, ScrollBehavior::Auto);
1647 }
1648}