servo-script 0.5.0

A component of the servo web-engine.
Documentation
/* 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 dom_struct::dom_struct;
use euclid::Point2D;
use js::context::JSContext;
use js::rust::HandleObject;
use keyboard_types::Modifiers;
use script_bindings::cell::DomRefCell;
use script_bindings::reflector::reflect_dom_object_with_proto;
use style::Atom;
use style_traits::CSSPixel;

use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
use crate::dom::bindings::codegen::Bindings::PointerEventBinding::{
    PointerEventInit, PointerEventMethods,
};
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::event::{EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::mouseevent::MouseEvent;
use crate::dom::window::Window;

/// <https://w3c.github.io/pointerevents/#dom-pointerevent-pointerid>
#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
pub(crate) enum PointerId {
    NonPointerDevice = -1,
    Mouse,
}

#[dom_struct]
pub(crate) struct PointerEvent {
    mouseevent: MouseEvent,
    pointer_id: Cell<i32>,
    width: Cell<i32>,
    height: Cell<i32>,
    pressure: Cell<f32>,
    tangential_pressure: Cell<f32>,
    tilt_x: Cell<i32>,
    tilt_y: Cell<i32>,
    twist: Cell<i32>,
    altitude_angle: Cell<f64>,
    azimuth_angle: Cell<f64>,
    pointer_type: DomRefCell<DOMString>,
    is_primary: Cell<bool>,
    coalesced_events: DomRefCell<Vec<Dom<PointerEvent>>>,
    predicted_events: DomRefCell<Vec<Dom<PointerEvent>>>,
}

impl PointerEvent {
    fn new_inherited() -> PointerEvent {
        PointerEvent {
            mouseevent: MouseEvent::new_inherited(),
            pointer_id: Cell::new(0),
            width: Cell::new(0),
            height: Cell::new(0),
            pressure: Cell::new(0.),
            tangential_pressure: Cell::new(0.),
            tilt_x: Cell::new(0),
            tilt_y: Cell::new(0),
            twist: Cell::new(0),
            altitude_angle: Cell::new(0.),
            azimuth_angle: Cell::new(0.),
            pointer_type: DomRefCell::new(DOMString::new()),
            is_primary: Cell::new(false),
            coalesced_events: DomRefCell::new(Vec::new()),
            predicted_events: DomRefCell::new(Vec::new()),
        }
    }

    fn new_uninitialized_with_proto(
        cx: &mut JSContext,
        window: &Window,
        proto: Option<HandleObject>,
    ) -> DomRoot<PointerEvent> {
        reflect_dom_object_with_proto(cx, Box::new(PointerEvent::new_inherited()), window, proto)
    }

    #[expect(clippy::too_many_arguments)]
    pub(crate) fn new(
        cx: &mut JSContext,
        window: &Window,
        event_type: Atom,
        can_bubble: EventBubbles,
        cancelable: EventCancelable,
        view: Option<&Window>,
        detail: i32,
        screen_point: Point2D<i32, CSSPixel>,
        client_point: Point2D<i32, CSSPixel>,
        page_point: Point2D<i32, CSSPixel>,
        modifiers: Modifiers,
        button: i16,
        buttons: u16,
        related_target: Option<&EventTarget>,
        point_in_target: Option<Point2D<f32, CSSPixel>>,
        pointer_id: i32,
        width: i32,
        height: i32,
        pressure: f32,
        tangential_pressure: f32,
        tilt_x: i32,
        tilt_y: i32,
        twist: i32,
        altitude_angle: f64,
        azimuth_angle: f64,
        pointer_type: DOMString,
        is_primary: bool,
        coalesced_events: Vec<DomRoot<PointerEvent>>,
        predicted_events: Vec<DomRoot<PointerEvent>>,
    ) -> DomRoot<PointerEvent> {
        Self::new_with_proto(
            cx,
            window,
            None,
            event_type,
            can_bubble,
            cancelable,
            view,
            detail,
            screen_point,
            client_point,
            page_point,
            modifiers,
            button,
            buttons,
            related_target,
            point_in_target,
            pointer_id,
            width,
            height,
            pressure,
            tangential_pressure,
            tilt_x,
            tilt_y,
            twist,
            altitude_angle,
            azimuth_angle,
            pointer_type,
            is_primary,
            coalesced_events,
            predicted_events,
        )
    }

    #[expect(clippy::too_many_arguments)]
    fn new_with_proto(
        cx: &mut JSContext,
        window: &Window,
        proto: Option<HandleObject>,
        event_type: Atom,
        can_bubble: EventBubbles,
        cancelable: EventCancelable,
        view: Option<&Window>,
        detail: i32,
        screen_point: Point2D<i32, CSSPixel>,
        client_point: Point2D<i32, CSSPixel>,
        page_point: Point2D<i32, CSSPixel>,
        modifiers: Modifiers,
        button: i16,
        buttons: u16,
        related_target: Option<&EventTarget>,
        point_in_target: Option<Point2D<f32, CSSPixel>>,
        pointer_id: i32,
        width: i32,
        height: i32,
        pressure: f32,
        tangential_pressure: f32,
        tilt_x: i32,
        tilt_y: i32,
        twist: i32,
        altitude_angle: f64,
        azimuth_angle: f64,
        pointer_type: DOMString,
        is_primary: bool,
        coalesced_events: Vec<DomRoot<PointerEvent>>,
        predicted_events: Vec<DomRoot<PointerEvent>>,
    ) -> DomRoot<PointerEvent> {
        let ev = PointerEvent::new_uninitialized_with_proto(cx, window, proto);
        ev.mouseevent.initialize_mouse_event(
            event_type,
            can_bubble,
            cancelable,
            view,
            detail,
            screen_point,
            client_point,
            page_point,
            modifiers,
            button,
            buttons,
            related_target,
            point_in_target,
        );
        ev.pointer_id.set(pointer_id);
        ev.width.set(width);
        ev.height.set(height);
        ev.pressure.set(pressure);
        ev.tangential_pressure.set(tangential_pressure);
        ev.tilt_x.set(tilt_x);
        ev.tilt_y.set(tilt_y);
        ev.twist.set(twist);
        ev.altitude_angle.set(altitude_angle);
        ev.azimuth_angle.set(azimuth_angle);
        *ev.pointer_type.borrow_mut() = pointer_type;
        ev.is_primary.set(is_primary);
        *ev.coalesced_events.borrow_mut() = coalesced_events
            .into_iter()
            .map(|event| event.as_traced())
            .collect();
        *ev.predicted_events.borrow_mut() = predicted_events
            .into_iter()
            .map(|event| event.as_traced())
            .collect();
        ev
    }
}

impl PointerEventMethods<crate::DomTypeHolder> for PointerEvent {
    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-constructor>
    fn Constructor(
        cx: &mut JSContext,
        window: &Window,
        proto: Option<HandleObject>,
        event_type: DOMString,
        init: &PointerEventInit,
    ) -> DomRoot<PointerEvent> {
        let bubbles = EventBubbles::from(init.parent.parent.parent.parent.bubbles);
        let cancelable = EventCancelable::from(init.parent.parent.parent.parent.cancelable);
        let scroll_offset = window.scroll_offset();
        let page_point = Point2D::new(
            scroll_offset.x as i32 + init.parent.clientX,
            scroll_offset.y as i32 + init.parent.clientY,
        );
        PointerEvent::new_with_proto(
            cx,
            window,
            proto,
            event_type.into(),
            bubbles,
            cancelable,
            init.parent.parent.parent.view.as_deref(),
            init.parent.parent.parent.detail,
            Point2D::new(init.parent.screenX, init.parent.screenY),
            Point2D::new(init.parent.clientX, init.parent.clientY),
            page_point,
            init.parent.parent.modifiers(),
            init.parent.button,
            init.parent.buttons,
            init.parent.relatedTarget.as_deref(),
            None,
            init.pointerId,
            init.width,
            init.height,
            *init.pressure,
            *init.tangentialPressure,
            init.tiltX.unwrap_or_default(),
            init.tiltY.unwrap_or_default(),
            init.twist,
            *init.altitudeAngle.unwrap_or_default(),
            *init.azimuthAngle.unwrap_or_default(),
            init.pointerType.clone(),
            init.isPrimary,
            init.coalescedEvents.clone(),
            init.predictedEvents.clone(),
        )
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-pointerid>
    fn PointerId(&self) -> i32 {
        self.pointer_id.get()
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-width>
    fn Width(&self) -> i32 {
        self.width.get()
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-height>
    fn Height(&self) -> i32 {
        self.height.get()
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-pressure>
    fn Pressure(&self) -> Finite<f32> {
        Finite::wrap(self.pressure.get())
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-tangentialpressure>
    fn TangentialPressure(&self) -> Finite<f32> {
        Finite::wrap(self.tangential_pressure.get())
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-tiltx>
    fn TiltX(&self) -> i32 {
        self.tilt_x.get()
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-tilty>
    fn TiltY(&self) -> i32 {
        self.tilt_y.get()
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-twist>
    fn Twist(&self) -> i32 {
        self.twist.get()
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-altitudeangle>
    fn AltitudeAngle(&self) -> Finite<f64> {
        Finite::wrap(self.altitude_angle.get())
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-azimuthangle>
    fn AzimuthAngle(&self) -> Finite<f64> {
        Finite::wrap(self.azimuth_angle.get())
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-pointertype>
    fn PointerType(&self) -> DOMString {
        self.pointer_type.borrow().clone()
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-isprimary>
    fn IsPrimary(&self) -> bool {
        self.is_primary.get()
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-getcoalescedevents>
    fn GetCoalescedEvents(&self) -> Vec<DomRoot<PointerEvent>> {
        self.coalesced_events
            .borrow()
            .iter()
            .map(|event| event.as_rooted())
            .collect()
    }

    /// <https://w3c.github.io/pointerevents/#dom-pointerevent-getpredictedevents>
    fn GetPredictedEvents(&self) -> Vec<DomRoot<PointerEvent>> {
        self.predicted_events
            .borrow()
            .iter()
            .map(|event| event.as_rooted())
            .collect()
    }

    /// <https://dom.spec.whatwg.org/#dom-event-istrusted>
    fn IsTrusted(&self) -> bool {
        self.mouseevent.IsTrusted()
    }
}