/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::Cell; use std::default::Default; use dom_struct::dom_struct; use embedder_traits::CompositorHitTestResult; use euclid::default::Point2D; use js::rust::HandleObject; use servo_config::pref; use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods; use crate::dom::bindings::codegen::Bindings::MouseEventBinding; use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; use crate::dom::bindings::error::Fallible; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto}; use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; use crate::dom::node::Node; use crate::dom::uievent::UIEvent; use crate::dom::window::Window; use crate::script_runtime::CanGc; /// #[dom_struct] pub(crate) struct MouseEvent { uievent: UIEvent, /// screen_x: Cell, /// screen_y: Cell, /// client_x: Cell, /// client_y: Cell, page_x: Cell, page_y: Cell, x: Cell, y: Cell, offset_x: Cell, offset_y: Cell, /// ctrl_key: Cell, /// shift_key: Cell, /// alt_key: Cell, /// meta_key: Cell, /// button: Cell, /// buttons: Cell, /// related_target: MutNullableDom, #[no_trace] point_in_target: Cell>>, } impl MouseEvent { pub(crate) fn new_inherited() -> MouseEvent { MouseEvent { uievent: UIEvent::new_inherited(), screen_x: Cell::new(0), screen_y: Cell::new(0), client_x: Cell::new(0), client_y: Cell::new(0), page_x: Cell::new(0), page_y: Cell::new(0), x: Cell::new(0), y: Cell::new(0), offset_x: Cell::new(0), offset_y: Cell::new(0), ctrl_key: Cell::new(false), shift_key: Cell::new(false), alt_key: Cell::new(false), meta_key: Cell::new(false), button: Cell::new(0), buttons: Cell::new(0), related_target: Default::default(), point_in_target: Cell::new(None), } } pub(crate) fn new_uninitialized(window: &Window, can_gc: CanGc) -> DomRoot { Self::new_uninitialized_with_proto(window, None, can_gc) } fn new_uninitialized_with_proto( window: &Window, proto: Option, can_gc: CanGc, ) -> DomRoot { reflect_dom_object_with_proto(Box::new(MouseEvent::new_inherited()), window, proto, can_gc) } #[allow(clippy::too_many_arguments)] pub(crate) fn new( window: &Window, type_: DOMString, can_bubble: EventBubbles, cancelable: EventCancelable, view: Option<&Window>, detail: i32, screen_x: i32, screen_y: i32, client_x: i32, client_y: i32, ctrl_key: bool, alt_key: bool, shift_key: bool, meta_key: bool, button: i16, buttons: u16, related_target: Option<&EventTarget>, point_in_target: Option>, can_gc: CanGc, ) -> DomRoot { Self::new_with_proto( window, None, type_, can_bubble, cancelable, view, detail, screen_x, screen_y, client_x, client_y, ctrl_key, alt_key, shift_key, meta_key, button, buttons, related_target, point_in_target, can_gc, ) } #[allow(clippy::too_many_arguments)] fn new_with_proto( window: &Window, proto: Option, type_: DOMString, can_bubble: EventBubbles, cancelable: EventCancelable, view: Option<&Window>, detail: i32, screen_x: i32, screen_y: i32, client_x: i32, client_y: i32, ctrl_key: bool, alt_key: bool, shift_key: bool, meta_key: bool, button: i16, buttons: u16, related_target: Option<&EventTarget>, point_in_target: Option>, can_gc: CanGc, ) -> DomRoot { let ev = MouseEvent::new_uninitialized_with_proto(window, proto, can_gc); ev.initialize_mouse_event( type_, can_bubble, cancelable, view, detail, screen_x, screen_y, client_x, client_y, ctrl_key, alt_key, shift_key, meta_key, button, buttons, related_target, point_in_target, ); ev } /// #[allow(clippy::too_many_arguments)] pub(crate) fn initialize_mouse_event( &self, type_: DOMString, can_bubble: EventBubbles, cancelable: EventCancelable, view: Option<&Window>, detail: i32, screen_x: i32, screen_y: i32, client_x: i32, client_y: i32, ctrl_key: bool, alt_key: bool, shift_key: bool, meta_key: bool, button: i16, buttons: u16, related_target: Option<&EventTarget>, point_in_target: Option>, ) { self.uievent.initialize_ui_event( type_, view.map(|window| window.upcast::()), can_bubble, cancelable, ); self.uievent.set_detail(detail); self.screen_x.set(screen_x); self.screen_y.set(screen_y); self.client_x.set(client_x); self.client_y.set(client_y); self.page_x.set(self.PageX()); self.page_y.set(self.PageY()); // skip setting flags as they are absent self.shift_key.set(shift_key); self.ctrl_key.set(ctrl_key); self.alt_key.set(alt_key); self.meta_key.set(meta_key); self.button.set(button); self.buttons.set(buttons); // skip step 3: Initialize PointerLock attributes for MouseEvent with event, // as movementX, movementY is absent self.related_target.set(related_target); // below is not in the spec self.point_in_target.set(point_in_target); } pub(crate) fn point_in_target(&self) -> Option> { self.point_in_target.get() } /// Create a [MouseEvent] triggered by the embedder pub(crate) fn for_platform_mouse_event( event: embedder_traits::MouseButtonEvent, pressed_mouse_buttons: u16, window: &Window, hit_test_result: &CompositorHitTestResult, can_gc: CanGc, ) -> DomRoot { let mouse_event_type_string = match event.action { embedder_traits::MouseButtonAction::Click => "click", embedder_traits::MouseButtonAction::Up => "mouseup", embedder_traits::MouseButtonAction::Down => "mousedown", }; let client_x = hit_test_result.point_in_viewport.x as i32; let client_y = hit_test_result.point_in_viewport.y as i32; let click_count = 1; let mouse_event = MouseEvent::new( window, mouse_event_type_string.into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(window), click_count, client_x, client_y, client_x, client_y, // TODO: Get real screen coordinates? false, false, false, false, event.button.into(), pressed_mouse_buttons, None, Some(hit_test_result.point_relative_to_item), can_gc, ); mouse_event.upcast::().set_trusted(true); mouse_event.upcast::().set_composed(true); mouse_event } } impl MouseEventMethods for MouseEvent { /// fn Constructor( window: &Window, proto: Option, can_gc: CanGc, type_: DOMString, init: &MouseEventBinding::MouseEventInit, ) -> Fallible> { let bubbles = EventBubbles::from(init.parent.parent.parent.bubbles); let cancelable = EventCancelable::from(init.parent.parent.parent.cancelable); let event = MouseEvent::new_with_proto( window, proto, type_, bubbles, cancelable, init.parent.parent.view.as_deref(), init.parent.parent.detail, init.screenX, init.screenY, init.clientX, init.clientY, init.parent.ctrlKey, init.parent.altKey, init.parent.shiftKey, init.parent.metaKey, init.button, init.buttons, init.relatedTarget.as_deref(), None, can_gc, ); event .upcast::() .set_composed(init.parent.parent.parent.composed); Ok(event) } /// fn ScreenX(&self) -> i32 { self.screen_x.get() } /// fn ScreenY(&self) -> i32 { self.screen_y.get() } /// fn ClientX(&self) -> i32 { self.client_x.get() } /// fn ClientY(&self) -> i32 { self.client_y.get() } /// fn PageX(&self) -> i32 { if self.upcast::().dispatching() { self.page_x.get() } else { let global = self.global(); let window = global.as_window(); window.current_viewport().origin.x.to_px() + self.client_x.get() } } /// fn PageY(&self) -> i32 { if self.upcast::().dispatching() { self.page_y.get() } else { let global = self.global(); let window = global.as_window(); window.current_viewport().origin.y.to_px() + self.client_y.get() } } /// fn X(&self) -> i32 { self.client_x.get() } /// fn Y(&self) -> i32 { self.client_y.get() } /// fn OffsetX(&self, can_gc: CanGc) -> i32 { let event = self.upcast::(); if event.dispatching() { match event.GetTarget() { Some(target) => { if let Some(node) = target.downcast::() { let rect = node.client_rect(can_gc); self.client_x.get() - rect.origin.x } else { self.offset_x.get() } }, None => self.offset_x.get(), } } else { self.PageX() } } /// fn OffsetY(&self, can_gc: CanGc) -> i32 { let event = self.upcast::(); if event.dispatching() { match event.GetTarget() { Some(target) => { if let Some(node) = target.downcast::() { let rect = node.client_rect(can_gc); self.client_y.get() - rect.origin.y } else { self.offset_y.get() } }, None => self.offset_y.get(), } } else { self.PageY() } } /// fn CtrlKey(&self) -> bool { self.ctrl_key.get() } /// fn ShiftKey(&self) -> bool { self.shift_key.get() } /// fn AltKey(&self) -> bool { self.alt_key.get() } /// fn MetaKey(&self) -> bool { self.meta_key.get() } /// fn Button(&self) -> i16 { self.button.get() } /// fn Buttons(&self) -> u16 { self.buttons.get() } /// fn GetRelatedTarget(&self) -> Option> { self.related_target.get() } // See discussion at: // - https://github.com/servo/servo/issues/6643 // - https://bugzilla.mozilla.org/show_bug.cgi?id=1186125 // This returns the same result as current gecko. // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which fn Which(&self) -> i32 { if pref!(dom_mouse_event_which_enabled) { (self.button.get() + 1) as i32 } else { 0 } } /// fn InitMouseEvent( &self, type_arg: DOMString, can_bubble_arg: bool, cancelable_arg: bool, view_arg: Option<&Window>, detail_arg: i32, screen_x_arg: i32, screen_y_arg: i32, client_x_arg: i32, client_y_arg: i32, ctrl_key_arg: bool, alt_key_arg: bool, shift_key_arg: bool, meta_key_arg: bool, button_arg: i16, related_target_arg: Option<&EventTarget>, ) { if self.upcast::().dispatching() { return; } self.upcast::().InitUIEvent( type_arg, can_bubble_arg, cancelable_arg, view_arg, detail_arg, ); self.screen_x.set(screen_x_arg); self.screen_y.set(screen_y_arg); self.client_x.set(client_x_arg); self.client_y.set(client_y_arg); self.ctrl_key.set(ctrl_key_arg); self.alt_key.set(alt_key_arg); self.shift_key.set(shift_key_arg); self.meta_key.set(meta_key_arg); self.button.set(button_arg); self.related_target.set(related_target_arg); } /// fn IsTrusted(&self) -> bool { self.uievent.IsTrusted() } }