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 base::generic_channel;
13use constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
14use embedder_traits::{
15 Cursor, EditingActionEvent, EmbedderMsg, GamepadEvent as EmbedderGamepadEvent,
16 GamepadSupportedHapticEffects, GamepadUpdateType, ImeEvent, InputEvent, InputEventAndId,
17 InputEventResult, KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction,
18 MouseButtonEvent, MouseLeftViewportEvent, ScrollEvent, TouchEvent as EmbedderTouchEvent,
19 TouchEventType, TouchId, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
20};
21use euclid::{Point2D, Vector2D};
22use ipc_channel::ipc;
23use js::jsapi::JSAutoRealm;
24use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
25use layout_api::{ScrollContainerQueryFlags, node_id_from_scroll_id};
26use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
27use script_bindings::codegen::GenericBindings::EventBinding::EventMethods;
28use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods;
29use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
30use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
31use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
32use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
33use script_bindings::inheritance::Castable;
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, TouchEventResult};
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 InputEventResult::default()
192 },
193 InputEvent::Wheel(wheel_event) => {
194 self.handle_wheel_event(wheel_event, &event, can_gc)
195 },
196 InputEvent::Keyboard(keyboard_event) => {
197 self.handle_keyboard_event(keyboard_event, can_gc)
198 },
199 InputEvent::Ime(ime_event) => self.handle_ime_event(ime_event, can_gc),
200 InputEvent::Gamepad(gamepad_event) => {
201 self.handle_gamepad_event(gamepad_event);
202 InputEventResult::default()
203 },
204 InputEvent::EditingAction(editing_action_event) => {
205 self.handle_editing_action(editing_action_event, can_gc)
206 },
207 InputEvent::Scroll(scroll_event) => {
208 self.handle_embedder_scroll_event(scroll_event);
209 InputEventResult::default()
210 },
211 };
212
213 self.notify_embedder_that_event_was_handled(event.event, result);
214 }
215 }
216
217 fn notify_embedder_that_event_was_handled(
218 &self,
219 event: InputEventAndId,
220 result: InputEventResult,
221 ) {
222 let id = event.id;
225 let trusted_window = Trusted::new(&*self.window);
226 self.window
227 .as_global_scope()
228 .task_manager()
229 .dom_manipulation_task_source()
230 .queue(task!(notify_webdriver_input_event_completed: move || {
231 let window = trusted_window.root();
232 window.send_to_embedder(
233 EmbedderMsg::InputEventHandled(window.webview_id(), id, result));
234 }));
235 }
236
237 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
238 if cursor == self.current_cursor.get() {
239 return;
240 }
241 self.current_cursor.set(cursor);
242 self.window.send_to_embedder(EmbedderMsg::SetCursor(
243 self.window.webview_id(),
244 cursor.unwrap_or_default(),
245 ));
246 }
247
248 fn handle_mouse_left_viewport_event(
249 &self,
250 input_event: &ConstellationInputEvent,
251 mouse_leave_event: &MouseLeftViewportEvent,
252 can_gc: CanGc,
253 ) {
254 if let Some(current_hover_target) = self.current_hover_target.get() {
255 let current_hover_target = current_hover_target.upcast::<Node>();
256 for element in current_hover_target
257 .inclusive_ancestors(ShadowIncluding::No)
258 .filter_map(DomRoot::downcast::<Element>)
259 {
260 element.set_hover_state(false);
261 element.set_active_state(false);
262 }
263
264 if let Some(hit_test_result) = self
265 .most_recent_mousemove_point
266 .get()
267 .and_then(|point| self.window.hit_test_from_point_in_viewport(point))
268 {
269 MouseEvent::new_simple(
270 &self.window,
271 FireMouseEventType::Out,
272 EventBubbles::Bubbles,
273 EventCancelable::Cancelable,
274 &hit_test_result,
275 input_event,
276 can_gc,
277 )
278 .upcast::<Event>()
279 .fire(current_hover_target.upcast(), can_gc);
280 self.handle_mouse_enter_leave_event(
281 DomRoot::from_ref(current_hover_target),
282 None,
283 FireMouseEventType::Leave,
284 &hit_test_result,
285 input_event,
286 can_gc,
287 );
288 }
289 }
290
291 if !mouse_leave_event.focus_moving_to_another_iframe {
296 self.window
300 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
301 self.set_cursor(None);
302 } else {
303 self.current_cursor.set(None);
304 }
305
306 self.current_hover_target.set(None);
307 self.most_recent_mousemove_point.set(None);
308 }
309
310 fn handle_mouse_enter_leave_event(
311 &self,
312 event_target: DomRoot<Node>,
313 related_target: Option<DomRoot<Node>>,
314 event_type: FireMouseEventType,
315 hit_test_result: &HitTestResult,
316 input_event: &ConstellationInputEvent,
317 can_gc: CanGc,
318 ) {
319 assert!(matches!(
320 event_type,
321 FireMouseEventType::Enter | FireMouseEventType::Leave
322 ));
323
324 let common_ancestor = match related_target.as_ref() {
325 Some(related_target) => event_target
326 .common_ancestor(related_target, ShadowIncluding::No)
327 .unwrap_or_else(|| DomRoot::from_ref(&*event_target)),
328 None => DomRoot::from_ref(&*event_target),
329 };
330
331 let mut targets = vec![];
334 let mut current = Some(event_target);
335 while let Some(node) = current {
336 if node == common_ancestor {
337 break;
338 }
339 current = node.GetParentNode();
340 targets.push(node);
341 }
342
343 if event_type == FireMouseEventType::Enter {
346 targets = targets.into_iter().rev().collect();
347 }
348
349 for target in targets {
350 MouseEvent::new_simple(
351 &self.window,
352 event_type,
353 EventBubbles::DoesNotBubble,
354 EventCancelable::NotCancelable,
355 hit_test_result,
356 input_event,
357 can_gc,
358 )
359 .upcast::<Event>()
360 .fire(target.upcast(), can_gc);
361 }
362 }
363
364 fn handle_native_mouse_move_event(&self, input_event: &ConstellationInputEvent, can_gc: CanGc) {
366 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
368 return;
369 };
370
371 self.set_cursor(Some(hit_test_result.cursor));
373
374 let Some(new_target) = hit_test_result
375 .node
376 .inclusive_ancestors(ShadowIncluding::No)
377 .filter_map(DomRoot::downcast::<Element>)
378 .next()
379 else {
380 return;
381 };
382
383 let target_has_changed = self
384 .current_hover_target
385 .get()
386 .is_none_or(|old_target| old_target != new_target);
387
388 if target_has_changed {
391 if let Some(old_target) = self.current_hover_target.get() {
393 let old_target_is_ancestor_of_new_target = old_target
394 .upcast::<Node>()
395 .is_ancestor_of(new_target.upcast::<Node>());
396
397 if !old_target_is_ancestor_of_new_target {
400 for element in old_target
401 .upcast::<Node>()
402 .inclusive_ancestors(ShadowIncluding::No)
403 .filter_map(DomRoot::downcast::<Element>)
404 {
405 element.set_hover_state(false);
406 element.set_active_state(false);
407 }
408 }
409
410 MouseEvent::new_simple(
411 &self.window,
412 FireMouseEventType::Out,
413 EventBubbles::Bubbles,
414 EventCancelable::Cancelable,
415 &hit_test_result,
416 input_event,
417 can_gc,
418 )
419 .upcast::<Event>()
420 .fire(old_target.upcast(), can_gc);
421
422 if !old_target_is_ancestor_of_new_target {
423 let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
424 let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
425 self.handle_mouse_enter_leave_event(
426 event_target,
427 moving_into,
428 FireMouseEventType::Leave,
429 &hit_test_result,
430 input_event,
431 can_gc,
432 );
433 }
434 }
435
436 for element in new_target
438 .upcast::<Node>()
439 .inclusive_ancestors(ShadowIncluding::No)
440 .filter_map(DomRoot::downcast::<Element>)
441 {
442 element.set_hover_state(true);
443 }
444
445 MouseEvent::new_simple(
446 &self.window,
447 FireMouseEventType::Over,
448 EventBubbles::Bubbles,
449 EventCancelable::Cancelable,
450 &hit_test_result,
451 input_event,
452 can_gc,
453 )
454 .upcast::<Event>()
455 .fire(new_target.upcast(), can_gc);
456
457 let moving_from = self
458 .current_hover_target
459 .get()
460 .map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
461 let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
462 self.handle_mouse_enter_leave_event(
463 event_target,
464 moving_from,
465 FireMouseEventType::Enter,
466 &hit_test_result,
467 input_event,
468 can_gc,
469 );
470 }
471
472 MouseEvent::new_simple(
475 &self.window,
476 FireMouseEventType::Move,
477 EventBubbles::Bubbles,
478 EventCancelable::Cancelable,
479 &hit_test_result,
480 input_event,
481 can_gc,
482 )
483 .upcast::<Event>()
484 .fire(new_target.upcast(), can_gc);
485
486 self.update_current_hover_target_and_status(Some(new_target));
487 self.most_recent_mousemove_point
488 .set(Some(hit_test_result.point_in_frame));
489 }
490
491 fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
492 let current_hover_target = self.current_hover_target.get();
493 if current_hover_target == new_hover_target {
494 return;
495 }
496
497 let previous_hover_target = self.current_hover_target.get();
498 self.current_hover_target.set(new_hover_target.as_deref());
499
500 if let Some(target) = self.current_hover_target.get() {
503 if let Some(anchor) = target
504 .upcast::<Node>()
505 .inclusive_ancestors(ShadowIncluding::No)
506 .filter_map(DomRoot::downcast::<HTMLAnchorElement>)
507 .next()
508 {
509 let status = anchor
510 .upcast::<Element>()
511 .get_attribute(&ns!(), &local_name!("href"))
512 .and_then(|href| {
513 let value = href.value();
514 let url = self.window.get_url();
515 url.join(&value).map(|url| url.to_string()).ok()
516 });
517 self.window
518 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
519 return;
520 }
521 }
522
523 if previous_hover_target.is_none_or(|previous_hover_target| {
528 previous_hover_target
529 .upcast::<Node>()
530 .inclusive_ancestors(ShadowIncluding::No)
531 .filter_map(DomRoot::downcast::<HTMLAnchorElement>)
532 .next()
533 .is_some()
534 }) {
535 self.window
536 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
537 }
538 }
539
540 pub(crate) fn handle_refresh_cursor(&self) {
541 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
542 return;
543 };
544
545 let Some(hit_test_result) = self
546 .window
547 .hit_test_from_point_in_viewport(most_recent_mousemove_point)
548 else {
549 return;
550 };
551
552 self.set_cursor(Some(hit_test_result.cursor));
553 }
554
555 fn handle_native_mouse_button_event(
558 &self,
559 event: MouseButtonEvent,
560 input_event: &ConstellationInputEvent,
561 can_gc: CanGc,
562 ) {
563 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
565 return;
566 };
567
568 debug!(
569 "{:?}: at {:?}",
570 event.action, hit_test_result.point_in_frame
571 );
572
573 let Some(element) = hit_test_result
574 .node
575 .inclusive_ancestors(ShadowIncluding::Yes)
576 .filter_map(DomRoot::downcast::<Element>)
577 .next()
578 else {
579 return;
580 };
581
582 let node = element.upcast::<Node>();
583 debug!("{:?} on {:?}", event.action, node.debug_str());
584
585 if element.is_actually_disabled() {
589 return;
590 }
591
592 let mouse_event_type_string = match event.action {
593 embedder_traits::MouseButtonAction::Up => "mouseup",
594 embedder_traits::MouseButtonAction::Down => "mousedown",
595 };
596 let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_mouse_event(
597 mouse_event_type_string,
598 event,
599 input_event.pressed_mouse_buttons,
600 &self.window,
601 &hit_test_result,
602 input_event.active_keyboard_modifiers,
603 can_gc,
604 ));
605
606 let activatable = element.as_maybe_activatable();
607 match event.action {
608 MouseButtonAction::Down => {
609 self.last_mouse_button_down_point
610 .set(Some(hit_test_result.point_in_frame));
611
612 if let Some(a) = activatable {
613 a.enter_formal_activation_state();
614 }
615
616 let target_el = element.find_focusable_shadow_host_if_necessary();
623
624 let document = self.window.Document();
625 document.begin_focus_transaction();
626
627 document.request_focus(None, FocusInitiator::Local, can_gc);
629 document.request_focus(target_el.as_deref(), FocusInitiator::Local, can_gc);
630
631 let result = dom_event.dispatch(node.upcast(), false, can_gc);
633
634 if result && document.has_focus_transaction() {
637 document.commit_focus_transaction(FocusInitiator::Local, can_gc);
638 }
639
640 if let MouseButton::Right = event.button {
643 self.maybe_show_context_menu(
644 node.upcast(),
645 &hit_test_result,
646 input_event,
647 can_gc,
648 );
649 }
650 },
651 MouseButtonAction::Up => {
653 if let Some(a) = activatable {
654 a.exit_formal_activation_state();
655 }
656
657 dom_event.dispatch(node.upcast(), false, can_gc);
661
662 self.maybe_trigger_click_for_mouse_button_down_event(
663 event,
664 input_event,
665 &hit_test_result,
666 &element,
667 can_gc,
668 );
669 },
670 }
671 }
672
673 fn maybe_trigger_click_for_mouse_button_down_event(
675 &self,
676 event: MouseButtonEvent,
677 input_event: &ConstellationInputEvent,
678 hit_test_result: &HitTestResult,
679 element: &Element,
680 can_gc: CanGc,
681 ) {
682 if event.button != MouseButton::Left {
683 return;
684 }
685 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
686 return;
687 };
688
689 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
690 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
691 if distance > maximum_click_distance {
692 return;
693 }
694
695 self.most_recently_clicked_element.set(Some(element));
700
701 element.set_click_in_progress(true);
702 let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_mouse_event(
703 "click",
704 event,
705 input_event.pressed_mouse_buttons,
706 &self.window,
707 hit_test_result,
708 input_event.active_keyboard_modifiers,
709 can_gc,
710 ));
711 let node = element.upcast::<Node>();
712 dom_event.dispatch(node.upcast(), false, can_gc);
713 element.set_click_in_progress(false);
714
715 self.maybe_fire_dblclick(node, hit_test_result, input_event, can_gc);
716 }
717
718 fn maybe_show_context_menu(
720 &self,
721 target: &EventTarget,
722 hit_test_result: &HitTestResult,
723 input_event: &ConstellationInputEvent,
724 can_gc: CanGc,
725 ) {
726 let menu_event = PointerEvent::new(
728 &self.window, DOMString::from("contextmenu"), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
735 hit_test_result.point_in_frame.to_i32(),
736 hit_test_result
737 .point_relative_to_initial_containing_block
738 .to_i32(),
739 input_event.active_keyboard_modifiers,
740 2i16, input_event.pressed_mouse_buttons,
742 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,
759 );
760
761 let result = menu_event.upcast::<Event>().fire(target, can_gc);
763
764 if result {
766 let (sender, receiver) =
767 generic_channel::channel().expect("Failed to create IPC channel.");
768 self.window.send_to_embedder(EmbedderMsg::ShowContextMenu(
769 self.window.webview_id(),
770 sender,
771 None,
772 vec![],
773 ));
774 let _ = receiver.recv().unwrap();
775 };
776 }
777
778 fn maybe_fire_dblclick(
779 &self,
780 target: &Node,
781 hit_test_result: &HitTestResult,
782 input_event: &ConstellationInputEvent,
783 can_gc: CanGc,
784 ) {
785 let now = Instant::now();
787 let point_in_frame = hit_test_result.point_in_frame;
788 let opt = self.last_click_info.borrow_mut().take();
789
790 if let Some((last_time, last_pos)) = opt {
791 let double_click_timeout =
792 Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
793 let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
794
795 let line = point_in_frame - last_pos;
797 let dist = (line.dot(line) as f64).sqrt();
798
799 if now.duration_since(last_time) < double_click_timeout &&
800 dist < double_click_distance_threshold as f64
801 {
802 let click_count = 2;
804
805 let event = MouseEvent::new(
806 &self.window,
807 DOMString::from("dblclick"),
808 EventBubbles::Bubbles,
809 EventCancelable::Cancelable,
810 Some(&self.window),
811 click_count,
812 point_in_frame.to_i32(),
813 point_in_frame.to_i32(),
814 hit_test_result
815 .point_relative_to_initial_containing_block
816 .to_i32(),
817 input_event.active_keyboard_modifiers,
818 0i16,
819 input_event.pressed_mouse_buttons,
820 None,
821 None,
822 can_gc,
823 );
824 event.upcast::<Event>().fire(target.upcast(), can_gc);
825
826 return;
829 }
830 }
831
832 *self.last_click_info.borrow_mut() = Some((now, point_in_frame));
834 }
835
836 fn handle_touch_event(
837 &self,
838 event: EmbedderTouchEvent,
839 input_event: &ConstellationInputEvent,
840 can_gc: CanGc,
841 ) {
842 let result = self.handle_touch_event_inner(event, input_event, can_gc);
843 if let (TouchEventResult::Processed(handled), true) = (result, event.is_cancelable()) {
844 let sequence_id = event.expect_sequence_id();
845 let result = if handled {
846 embedder_traits::TouchEventResult::DefaultAllowed(sequence_id, event.event_type)
847 } else {
848 embedder_traits::TouchEventResult::DefaultPrevented(sequence_id, event.event_type)
849 };
850 self.window
851 .send_to_constellation(ScriptToConstellationMessage::TouchEventProcessed(result));
852 }
853 }
854
855 fn handle_touch_event_inner(
856 &self,
857 event: EmbedderTouchEvent,
858 input_event: &ConstellationInputEvent,
859 can_gc: CanGc,
860 ) -> TouchEventResult {
861 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
863 self.update_active_touch_points_when_early_return(event);
864 return TouchEventResult::Forwarded;
865 };
866
867 let TouchId(identifier) = event.id;
868 let event_name = match event.event_type {
869 TouchEventType::Down => "touchstart",
870 TouchEventType::Move => "touchmove",
871 TouchEventType::Up => "touchend",
872 TouchEventType::Cancel => "touchcancel",
873 };
874
875 let Some(el) = hit_test_result
876 .node
877 .inclusive_ancestors(ShadowIncluding::No)
878 .filter_map(DomRoot::downcast::<Element>)
879 .next()
880 else {
881 self.update_active_touch_points_when_early_return(event);
882 return TouchEventResult::Forwarded;
883 };
884
885 let target = DomRoot::upcast::<EventTarget>(el);
886 let window = &*self.window;
887
888 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
889 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
890 let page_x =
891 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
892 let page_y =
893 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
894
895 let touch = Touch::new(
896 window, identifier, &target, client_x,
897 client_y, client_x, client_y, page_x, page_y, can_gc,
899 );
900
901 match event.event_type {
902 TouchEventType::Down => {
903 self.active_touch_points
905 .borrow_mut()
906 .push(Dom::from_ref(&*touch));
907 },
908 TouchEventType::Move => {
909 let mut active_touch_points = self.active_touch_points.borrow_mut();
911 match active_touch_points
912 .iter_mut()
913 .find(|t| t.Identifier() == identifier)
914 {
915 Some(t) => *t = Dom::from_ref(&*touch),
916 None => warn!("Got a touchmove event for a non-active touch point"),
917 }
918 },
919 TouchEventType::Up | TouchEventType::Cancel => {
920 let mut active_touch_points = self.active_touch_points.borrow_mut();
922 match active_touch_points
923 .iter()
924 .position(|t| t.Identifier() == identifier)
925 {
926 Some(i) => {
927 active_touch_points.swap_remove(i);
928 },
929 None => warn!("Got a touchend event for a non-active touch point"),
930 }
931 },
932 }
933
934 rooted_vec!(let mut target_touches);
935 let touches = {
936 let touches = self.active_touch_points.borrow();
937 target_touches.extend(touches.iter().filter(|t| t.Target() == target).cloned());
938 TouchList::new(window, touches.r(), can_gc)
939 };
940
941 let event = TouchEvent::new(
942 window,
943 DOMString::from(event_name),
944 EventBubbles::Bubbles,
945 EventCancelable::from(event.is_cancelable()),
946 EventComposed::Composed,
947 Some(window),
948 0i32,
949 &touches,
950 &TouchList::new(window, from_ref(&&*touch), can_gc),
951 &TouchList::new(window, target_touches.r(), can_gc),
952 false,
954 false,
955 false,
956 false,
957 can_gc,
958 );
959
960 TouchEventResult::Processed(event.upcast::<Event>().fire(&target, can_gc))
961 }
962
963 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
965 match event.event_type {
966 TouchEventType::Down => {
967 },
971 TouchEventType::Move => {
972 },
975 TouchEventType::Up | TouchEventType::Cancel => {
976 let mut active_touch_points = self.active_touch_points.borrow_mut();
978 match active_touch_points
979 .iter()
980 .position(|t| t.Identifier() == event.id.0)
981 {
982 Some(i) => {
983 active_touch_points.swap_remove(i);
984 },
985 None => warn!("Got a touchend event for a non-active touch point"),
986 }
987 },
988 }
989 }
990
991 fn handle_keyboard_event(
993 &self,
994 keyboard_event: EmbedderKeyboardEvent,
995 can_gc: CanGc,
996 ) -> InputEventResult {
997 let document = self.window.Document();
998 let focused = document.get_focused_element();
999 let body = document.GetBody();
1000
1001 let target = match (&focused, &body) {
1002 (Some(focused), _) => focused.upcast(),
1003 (&None, Some(body)) => body.upcast(),
1004 (&None, &None) => self.window.upcast(),
1005 };
1006
1007 let keyevent = KeyboardEvent::new(
1008 &self.window,
1009 DOMString::from(keyboard_event.event.state.event_type()),
1010 true,
1011 true,
1012 Some(&self.window),
1013 0,
1014 keyboard_event.event.key.clone(),
1015 DOMString::from(keyboard_event.event.code.to_string()),
1016 keyboard_event.event.location as u32,
1017 keyboard_event.event.repeat,
1018 keyboard_event.event.is_composing,
1019 keyboard_event.event.modifiers,
1020 0,
1021 keyboard_event.event.key.legacy_keycode(),
1022 can_gc,
1023 );
1024
1025 let event = keyevent.upcast::<Event>();
1026 event.fire(target, can_gc);
1027
1028 let mut flags = event.flags();
1029 if flags.contains(EventFlags::Canceled) {
1030 return flags.into();
1031 }
1032
1033 let is_character_value_key = matches!(
1039 keyboard_event.event.key,
1040 Key::Character(_) | Key::Named(NamedKey::Enter)
1041 );
1042 if keyboard_event.event.state == KeyState::Down &&
1043 is_character_value_key &&
1044 !keyboard_event.event.is_composing
1045 {
1046 let keypress_event = KeyboardEvent::new(
1048 &self.window,
1049 DOMString::from("keypress"),
1050 true,
1051 true,
1052 Some(&self.window),
1053 0,
1054 keyboard_event.event.key.clone(),
1055 DOMString::from(keyboard_event.event.code.to_string()),
1056 keyboard_event.event.location as u32,
1057 keyboard_event.event.repeat,
1058 keyboard_event.event.is_composing,
1059 keyboard_event.event.modifiers,
1060 keyboard_event.event.key.legacy_charcode(),
1061 0,
1062 can_gc,
1063 );
1064 let event = keypress_event.upcast::<Event>();
1065 event.fire(target, can_gc);
1066 flags = event.flags();
1067 }
1068
1069 if flags.contains(EventFlags::Canceled) {
1070 return flags.into();
1071 }
1072
1073 if (keyboard_event.event.key == Key::Named(NamedKey::Enter) ||
1079 keyboard_event.event.code == Code::Space) &&
1080 keyboard_event.event.state == KeyState::Up
1081 {
1082 if let Some(elem) = target.downcast::<Element>() {
1083 elem.upcast::<Node>()
1084 .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
1085 }
1086 }
1087
1088 flags.into()
1089 }
1090
1091 fn handle_ime_event(&self, event: ImeEvent, can_gc: CanGc) -> InputEventResult {
1092 let document = self.window.Document();
1093 let composition_event = match event {
1094 ImeEvent::Dismissed => {
1095 document.request_focus(
1096 document.GetBody().as_ref().map(|e| e.upcast()),
1097 FocusInitiator::Local,
1098 can_gc,
1099 );
1100 return Default::default();
1101 },
1102 ImeEvent::Composition(composition_event) => composition_event,
1103 };
1104
1105 let focused = document.get_focused_element();
1110 let target = if let Some(elem) = &focused {
1111 elem.upcast()
1112 } else {
1113 return Default::default();
1115 };
1116
1117 let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1118 let event = CompositionEvent::new(
1119 &self.window,
1120 DOMString::from(composition_event.state.event_type()),
1121 true,
1122 cancelable,
1123 Some(&self.window),
1124 0,
1125 DOMString::from(composition_event.data),
1126 can_gc,
1127 );
1128
1129 let event = event.upcast::<Event>();
1130 event.fire(target, can_gc);
1131 event.flags().into()
1132 }
1133
1134 fn handle_wheel_event(
1135 &self,
1136 event: EmbedderWheelEvent,
1137 input_event: &ConstellationInputEvent,
1138 can_gc: CanGc,
1139 ) -> InputEventResult {
1140 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1142 return Default::default();
1143 };
1144
1145 let Some(el) = hit_test_result
1146 .node
1147 .inclusive_ancestors(ShadowIncluding::No)
1148 .filter_map(DomRoot::downcast::<Element>)
1149 .next()
1150 else {
1151 return Default::default();
1152 };
1153
1154 let node = el.upcast::<Node>();
1155 let wheel_event_type_string = "wheel".to_owned();
1156 debug!(
1157 "{}: on {:?} at {:?}",
1158 wheel_event_type_string,
1159 node.debug_str(),
1160 hit_test_result.point_in_frame
1161 );
1162
1163 let dom_event = WheelEvent::new(
1165 &self.window,
1166 DOMString::from(wheel_event_type_string),
1167 EventBubbles::Bubbles,
1168 EventCancelable::Cancelable,
1169 Some(&self.window),
1170 0i32,
1171 hit_test_result.point_in_frame.to_i32(),
1172 hit_test_result.point_in_frame.to_i32(),
1173 hit_test_result
1174 .point_relative_to_initial_containing_block
1175 .to_i32(),
1176 input_event.active_keyboard_modifiers,
1177 0i16,
1178 input_event.pressed_mouse_buttons,
1179 None,
1180 None,
1181 Finite::wrap(-event.delta.x),
1186 Finite::wrap(-event.delta.y),
1187 Finite::wrap(-event.delta.z),
1188 event.delta.mode as u32,
1189 can_gc,
1190 );
1191
1192 let dom_event = dom_event.upcast::<Event>();
1193 dom_event.set_trusted(true);
1194 dom_event.fire(node.upcast(), can_gc);
1195
1196 dom_event.flags().into()
1197 }
1198
1199 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1200 match gamepad_event {
1201 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1202 self.handle_gamepad_connect(
1203 index.0,
1204 name,
1205 bounds.axis_bounds,
1206 bounds.button_bounds,
1207 supported_haptic_effects,
1208 );
1209 },
1210 EmbedderGamepadEvent::Disconnected(index) => {
1211 self.handle_gamepad_disconnect(index.0);
1212 },
1213 EmbedderGamepadEvent::Updated(index, update_type) => {
1214 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1215 },
1216 };
1217 }
1218
1219 fn handle_gamepad_connect(
1221 &self,
1222 _index: usize,
1226 name: String,
1227 axis_bounds: (f64, f64),
1228 button_bounds: (f64, f64),
1229 supported_haptic_effects: GamepadSupportedHapticEffects,
1230 ) {
1231 let trusted_window = Trusted::new(&*self.window);
1234 self.window
1235 .upcast::<GlobalScope>()
1236 .task_manager()
1237 .gamepad_task_source()
1238 .queue(task!(gamepad_connected: move || {
1239 let window = trusted_window.root();
1240
1241 let navigator = window.Navigator();
1242 let selected_index = navigator.select_gamepad_index();
1243 let gamepad = Gamepad::new(
1244 &window,
1245 selected_index,
1246 name,
1247 "standard".into(),
1248 axis_bounds,
1249 button_bounds,
1250 supported_haptic_effects,
1251 false,
1252 CanGc::note(),
1253 );
1254 navigator.set_gamepad(selected_index as usize, &gamepad, CanGc::note());
1255 }));
1256 }
1257
1258 fn handle_gamepad_disconnect(&self, index: usize) {
1260 let trusted_window = Trusted::new(&*self.window);
1261 self.window
1262 .upcast::<GlobalScope>()
1263 .task_manager()
1264 .gamepad_task_source()
1265 .queue(task!(gamepad_disconnected: move || {
1266 let window = trusted_window.root();
1267 let navigator = window.Navigator();
1268 if let Some(gamepad) = navigator.get_gamepad(index) {
1269 if window.Document().is_fully_active() {
1270 gamepad.update_connected(false, gamepad.exposed(), CanGc::note());
1271 navigator.remove_gamepad(index);
1272 }
1273 }
1274 }));
1275 }
1276
1277 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1279 let trusted_window = Trusted::new(&*self.window);
1280
1281 self.window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1283 task!(update_gamepad_state: move || {
1284 let window = trusted_window.root();
1285 let navigator = window.Navigator();
1286 if let Some(gamepad) = navigator.get_gamepad(index) {
1287 let current_time = window.Performance().Now();
1288 gamepad.update_timestamp(*current_time);
1289 match update_type {
1290 GamepadUpdateType::Axis(index, value) => {
1291 gamepad.map_and_normalize_axes(index, value);
1292 },
1293 GamepadUpdateType::Button(index, value) => {
1294 gamepad.map_and_normalize_buttons(index, value);
1295 }
1296 };
1297 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1298 navigator.set_has_gamepad_gesture(true);
1299 navigator.GetGamepads()
1300 .iter()
1301 .filter_map(|g| g.as_ref())
1302 .for_each(|gamepad| {
1303 gamepad.set_exposed(true);
1304 gamepad.update_timestamp(*current_time);
1305 let new_gamepad = Trusted::new(&**gamepad);
1306 if window.Document().is_fully_active() {
1307 window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1308 task!(update_gamepad_connect: move || {
1309 let gamepad = new_gamepad.root();
1310 gamepad.notify_event(GamepadEventType::Connected, CanGc::note());
1311 })
1312 );
1313 }
1314 });
1315 }
1316 }
1317 })
1318 );
1319 }
1320
1321 fn handle_editing_action(&self, action: EditingActionEvent, can_gc: CanGc) -> InputEventResult {
1323 let clipboard_event_type = match action {
1324 EditingActionEvent::Copy => ClipboardEventType::Copy,
1325 EditingActionEvent::Cut => ClipboardEventType::Cut,
1326 EditingActionEvent::Paste => ClipboardEventType::Paste,
1327 };
1328
1329 let script_triggered = false;
1331
1332 let script_may_access_clipboard = false;
1336
1337 if script_triggered && !script_may_access_clipboard {
1339 return InputEventResult::empty();
1340 }
1341
1342 let clipboard_event = ClipboardEvent::new(
1344 &self.window,
1345 None,
1346 DOMString::from(clipboard_event_type.as_str()),
1347 EventBubbles::Bubbles,
1348 EventCancelable::Cancelable,
1349 None,
1350 can_gc,
1351 );
1352 self.fire_clipboard_event(&clipboard_event, clipboard_event_type, can_gc);
1353
1354 let event = clipboard_event.upcast::<Event>();
1357 if !event.IsTrusted() {
1358 return event.flags().into();
1359 }
1360
1361 if event.DefaultPrevented() {
1363 match &*event.Type().str() {
1364 "copy" => {
1365 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1368 let drag_data_store =
1369 clipboard_data.data_store().expect("This shouldn't fail");
1370 self.write_content_to_the_clipboard(&drag_data_store);
1371 }
1372 },
1373 "cut" => {
1374 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1377 let drag_data_store =
1378 clipboard_data.data_store().expect("This shouldn't fail");
1379 self.write_content_to_the_clipboard(&drag_data_store);
1380 }
1381
1382 self.fire_clipboardchange_event(can_gc);
1384 },
1385 "paste" => (),
1389 _ => (),
1390 }
1391 }
1392
1393 event.flags().into()
1396 }
1397
1398 fn fire_clipboard_event(
1400 &self,
1401 event: &ClipboardEvent,
1402 action: ClipboardEventType,
1403 can_gc: CanGc,
1404 ) {
1405 let mut drag_data_store = DragDataStore::new();
1408
1409 let trusted = true;
1413
1414 let document = self.window.Document();
1416 let focused = document.get_focused_element();
1417 let body = document.GetBody();
1418
1419 let target = match (&focused, &body) {
1420 (Some(focused), _) => focused.upcast(),
1421 (&None, Some(body)) => body.upcast(),
1422 (&None, &None) => self.window.upcast(),
1423 };
1424 match action {
1428 ClipboardEventType::Copy | ClipboardEventType::Cut => {
1429 drag_data_store.set_mode(Mode::ReadWrite);
1431 },
1432 ClipboardEventType::Paste => {
1433 let (sender, receiver) = ipc::channel().unwrap();
1434 self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
1435 self.window.webview_id(),
1436 sender,
1437 ));
1438 let text_contents = receiver
1439 .recv()
1440 .map(Result::unwrap_or_default)
1441 .unwrap_or_default();
1442
1443 drag_data_store.set_mode(Mode::ReadOnly);
1445 if trusted {
1447 let data = DOMString::from(text_contents.to_string());
1451 let type_ = DOMString::from("text/plain");
1452 let _ = drag_data_store.add(Kind::Text { data, type_ });
1453
1454 }
1460 },
1461 ClipboardEventType::Change => (),
1462 }
1463
1464 let clipboard_event_data = DataTransfer::new(
1466 &self.window,
1467 Rc::new(RefCell::new(Some(drag_data_store))),
1468 can_gc,
1469 );
1470
1471 event.set_clipboard_data(Some(&clipboard_event_data));
1473 let event = event.upcast::<Event>();
1474 event.set_trusted(trusted);
1476 event.set_composed(true);
1478 event.dispatch(target, false, can_gc);
1480 }
1481
1482 pub(crate) fn fire_clipboardchange_event(&self, can_gc: CanGc) {
1483 let clipboardchange_event = ClipboardEvent::new(
1484 &self.window,
1485 None,
1486 DOMString::from("clipboardchange"),
1487 EventBubbles::Bubbles,
1488 EventCancelable::Cancelable,
1489 None,
1490 can_gc,
1491 );
1492 self.fire_clipboard_event(&clipboardchange_event, ClipboardEventType::Change, can_gc);
1493 }
1494
1495 fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
1497 if drag_data_store.list_len() > 0 {
1499 self.window
1501 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1502 for item in drag_data_store.iter_item_list() {
1504 match item {
1505 Kind::Text { data, .. } => {
1506 self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
1510 self.window.webview_id(),
1511 data.to_string(),
1512 ));
1513 },
1514 Kind::File { .. } => {
1515 },
1519 }
1520 }
1521 } else {
1522 if drag_data_store.clear_was_called {
1524 self.window
1526 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1527 }
1530 }
1531 }
1532
1533 #[allow(unsafe_code)]
1536 fn handle_embedder_scroll_event(&self, event: ScrollEvent) {
1537 let document = self.window.Document();
1539 if event.external_id.is_root() {
1540 document.handle_viewport_scroll_event();
1541 } else {
1542 let Some(node_id) = node_id_from_scroll_id(event.external_id.0 as usize) else {
1544 return;
1545 };
1546 let node = unsafe {
1547 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
1548 };
1549 let Some(element) = node
1550 .inclusive_ancestors(ShadowIncluding::No)
1551 .filter_map(DomRoot::downcast::<Element>)
1552 .next()
1553 else {
1554 return;
1555 };
1556
1557 document.handle_element_scroll_event(&element);
1558 }
1559 }
1560
1561 pub(crate) fn run_default_keyboard_event_handler(&self, event: &KeyboardEvent) {
1562 if event.upcast::<Event>().type_() != atom!("keydown") {
1563 return;
1564 }
1565 if !event.modifiers().is_empty() {
1566 return;
1567 }
1568 let scroll = match event.key() {
1569 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
1570 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
1571 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
1572 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
1573 Key::Named(NamedKey::End) => KeyboardScroll::End,
1574 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
1575 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
1576 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
1577 _ => return,
1578 };
1579 self.do_keyboard_scroll(scroll);
1580 }
1581
1582 pub(crate) fn do_keyboard_scroll(&self, scroll: KeyboardScroll) {
1583 let scroll_axis = match scroll {
1584 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
1585 _ => ScrollingBoxAxis::Y,
1586 };
1587
1588 let document = self.window.Document();
1589 let mut scrolling_box = document
1590 .get_focused_element()
1591 .or(self.most_recently_clicked_element.get())
1592 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
1593 .unwrap_or_else(|| {
1594 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
1595 });
1596
1597 while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
1598 if scrolling_box.is_viewport() {
1600 break;
1601 }
1602 let parent = scrolling_box.parent().unwrap_or_else(|| {
1603 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
1604 });
1605 scrolling_box = parent;
1606 }
1607
1608 if !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
1611 assert!(scrolling_box.is_viewport());
1612
1613 let window_proxy = document.window().window_proxy();
1614 if let Some(iframe) = window_proxy.frame_element() {
1615 let cx = GlobalScope::get_cx();
1618 let iframe_window = iframe.owner_window();
1619 let _ac = JSAutoRealm::new(*cx, iframe_window.reflector().get_jsobject().get());
1620 iframe_window
1621 .Document()
1622 .event_handler()
1623 .do_keyboard_scroll(scroll);
1624 } else if let Some(parent_pipeline) = self.window.parent_info() {
1625 document.window().send_to_constellation(
1629 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
1630 );
1631 };
1632 return;
1633 }
1634
1635 const LINE_HEIGHT: f32 = 76.0;
1636 const LINE_WIDTH: f32 = 76.0;
1637
1638 let current_scroll_offset = scrolling_box.scroll_position();
1639 let delta = match scroll {
1640 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
1641 KeyboardScroll::End => Vector2D::new(
1642 0.0,
1643 -current_scroll_offset.y + scrolling_box.content_size().height -
1644 scrolling_box.size().height,
1645 ),
1646 KeyboardScroll::PageDown => {
1647 Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
1648 },
1649 KeyboardScroll::PageUp => {
1650 Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
1651 },
1652 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
1653 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
1654 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
1655 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
1656 };
1657
1658 scrolling_box.scroll_to(delta + current_scroll_offset, ScrollBehavior::Auto);
1659 }
1660}