Repository: servo/servo
File: components/script/dom/document_event_handler.rs (Rust)

Code before:
        );
        pointer_event
            .upcast::<Event>()
            .fire(&current_target, can_gc);

        let (touch_dispatch_target, changed_touch) = match event.event_type {
            TouchEventType::Down => {
                // Add a new touch point
                self.active_touch_points
                    .borrow_mut()
                    .push(Dom::from_ref(&*pointer_touch));
                (current_target, pointer_touch)
            },
            _ => {
                // https://w3c.github.io/touch-events/#dfn-touchend
                // For move/up/cancel:
                // The target of this event must be the same Element on which the touch
                // point started when it was first placed on the surface,
                // even if the touch point has since moved outside
                // the interactive area of the target element.
                let mut active = self.active_touch_points.borrow_mut();
                let index = match active.iter().position(|t| t.Identifier() == identifier) {
                    Some(i) => i,
                    None => {
                        warn!("No active touch point for {:?}", event.event_type);
                        return Default::default();
                    },
                };
                // original target from touchstart
                let original_target = active[index].Target();

                let changed_touch = Touch::new(
                    window,
                    identifier,
                    &original_target,
                    client_x,
                    client_y,
                    client_x,
                    client_y,
                    page_x,
                    page_y,
                    can_gc,
                );

                // Update or remove the stored touch
                match event.event_type {
                    TouchEventType::Move => {
                        active[index] = Dom::from_ref(&*changed_touch);
                    },
                    TouchEventType::Up | TouchEventType::Cancel => {
                        active.swap_remove(index);

Review comment (bug):
> Also, previously touchend can be fired after touchcancel, which was wrong and fixed in this PR.

It is fixed here. Previously we only `warn!` but did not return. We have to return in this PR because we need to know the initial `touchstart` target.

As a follow-up, we should prevent this at `paint` layer.

Code after:
        pointer_event
            .upcast::<Event>()
            .fire(&current_target, can_gc);

        let (touch_dispatch_target, changed_touch) = match event.event_type {
            TouchEventType::Down => {
                // Add a new touch point
                self.active_touch_points
                    .borrow_mut()
                    .push(Dom::from_ref(&*pointer_touch));
                (current_target, pointer_touch)
            },
            _ => {
                // From <https://w3c.github.io/touch-events/#dfn-touchend>:
                // > For move/up/cancel:
                // > The target of this event must be the same Element on which the touch
                // > point started when it was first placed on the surface, even if the touch point
                // > has since moved outside the interactive area of the target element.
                let mut active_touch_points = self.active_touch_points.borrow_mut();
                let Some(index) = active_touch_points
                    .iter()
                    .position(|point| point.Identifier() == identifier)
                else {
                    warn!("No active touch point for {:?}", event.event_type);
                    return Default::default();
                };
                // This is the original target that was selected during `touchstart` event handling.
                let original_target = active_touch_points[index].Target();

                let touch_with_touchstart_target = Touch::new(
                    window,
                    identifier,
                    &original_target,
                    client_x,
                    client_y,
                    client_x,
                    client_y,
                    page_x,
                    page_y,
                    can_gc,
                );

                // Update or remove the stored touch
                match event.event_type {
                    TouchEventType::Move => {
                        active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
                    },
                    TouchEventType::Up | TouchEventType::Cancel => {
                        active_touch_points.swap_remove(index);
                        self.remove_pointer_id_for_touch(identifier);
                    },