Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions components/script/dom/document/document_event_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@ impl DocumentEventHandler {

// Send pointermove event before mousemove.
let pointer_event = mouse_event.to_pointer_event(Atom::from("pointermove"), can_gc);
pointer_event.upcast::<Event>().set_composed(true);
pointer_event
.upcast::<Event>()
.fire(new_target.upcast(), can_gc);
Expand Down Expand Up @@ -840,8 +841,8 @@ impl DocumentEventHandler {
embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
};

// From <https://w3c.github.io/uievents/#event-type-mousedown>
// and <https://w3c.github.io/uievents/#event-type-mouseup>:
// From <https://w3c.github.io/pointerevents/#dfn-mousedown>
// and <https://w3c.github.io/pointerevents/#mouseup>:
//
// UIEvent.detail: indicates the current click count incremented by one. For
// example, if no click happened before the mousedown, detail will contain
Expand Down Expand Up @@ -918,7 +919,7 @@ impl DocumentEventHandler {
);
}
},
// https://w3c.github.io/uievents/#handle-native-mouse-up
// https://w3c.github.io/pointerevents/#dfn-handle-native-mouse-up
MouseButtonAction::Up => {
// Step 6. Dispatch pointerup event.
let down_button_count = self.down_button_count.get();
Expand Down Expand Up @@ -960,8 +961,8 @@ impl DocumentEventHandler {
}
}

/// <https://w3c.github.io/uievents/#handle-native-mouse-click>
/// <https://w3c.github.io/uievents/#event-type-dblclick>
/// <https://w3c.github.io/pointerevents/#handle-native-mouse-click>
/// <https://w3c.github.io/pointerevents/#handle-native-mouse-double-click>
fn maybe_trigger_click_for_mouse_button_down_event(
&self,
event: MouseButtonEvent,
Expand All @@ -984,7 +985,7 @@ impl DocumentEventHandler {
return;
}

// From <https://w3c.github.io/uievents/#event-type-click>
// From <https://w3c.github.io/pointerevents/#click>
// > The click event type MUST be dispatched on the topmost event target indicated by the
// > pointer, when the user presses down and releases the primary pointer button.
// For nodes inside a text input UA shadow DOM, dispatch dblclick at the shadow host.
Expand Down Expand Up @@ -1032,15 +1033,15 @@ impl DocumentEventHandler {
}
}

/// <https://www.w3.org/TR/uievents/#maybe-show-context-menu>
/// <https://www.w3.org/TR/pointerevents4/#maybe-show-context-menu>
fn maybe_show_context_menu(
&self,
target: &EventTarget,
hit_test_result: &HitTestResult,
input_event: &ConstellationInputEvent,
can_gc: CanGc,
) {
// <https://w3c.github.io/uievents/#contextmenu>
// <https://w3c.github.io/pointerevents/#contextmenu>
let menu_event = PointerEvent::new(
&self.window, // window
"contextmenu".into(), // type
Expand Down Expand Up @@ -1074,6 +1075,7 @@ impl DocumentEventHandler {
vec![], // predicted_events
can_gc,
);
menu_event.upcast::<Event>().set_composed(true);

// Step 3. Let result = dispatch menuevent at target.
let result = menu_event.upcast::<Event>().fire(target, can_gc);
Expand Down Expand Up @@ -1372,6 +1374,12 @@ impl DocumentEventHandler {
);

let event = keyevent.upcast::<Event>();

// FIXME: https://github.com/servo/servo/issues/43809
if event.type_() != atom!("keydown") {
event.set_composed(true);
}

event.fire(target, can_gc);

let mut flags = event.flags();
Expand Down Expand Up @@ -1399,6 +1407,7 @@ impl DocumentEventHandler {
&keyboard_event.event,
can_gc,
);
keypress_event.upcast::<Event>().set_composed(true);
let event = keypress_event.upcast::<Event>();
event.fire(target, can_gc);
flags = event.flags();
Expand Down Expand Up @@ -1507,6 +1516,7 @@ impl DocumentEventHandler {

let dom_event = dom_event.upcast::<Event>();
dom_event.set_trusted(true);
dom_event.set_composed(true);
dom_event.fire(node.upcast(), can_gc);

dom_event.flags().into()
Expand Down
56 changes: 36 additions & 20 deletions components/script/dom/event/mouseevent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ impl MouseEvent {
ev
}

/// <https://w3c.github.io/uievents/#initialize-a-mouseevent>
/// <https://w3c.github.io/pointerevents/#initialize-a-mouseevent>
#[expect(clippy::too_many_arguments)]
pub(crate) fn initialize_mouse_event(
&self,
Expand Down Expand Up @@ -230,7 +230,7 @@ impl MouseEvent {
can_gc: CanGc,
) -> DomRoot<Self> {
// These values come from the event tables in
// <https://w3c.github.io/uievents/#events-mouse-types>.
// <https://w3c.github.io/pointerevents/#mouse-event-types>.
let (bubbles, cancelable, composed) = match event_name {
FireMouseEventType::Move | FireMouseEventType::Over | FireMouseEventType::Out => {
(EventBubbles::Bubbles, EventCancelable::Cancelable, true)
Expand Down Expand Up @@ -269,8 +269,9 @@ impl MouseEvent {
mouse_event
}

/// Create a [MouseEvent] triggered by the embedder
/// <https://w3c.github.io/uievents/#create-a-cancelable-mouseevent-id>
/// Create a [MouseEvent] triggered by the embedder.
///
/// <https://w3c.github.io/pointerevents/#create-a-cancelable-mouseevent>
#[expect(clippy::too_many_arguments)]
pub(crate) fn for_platform_button_event(
event_type: Atom,
Expand Down Expand Up @@ -341,10 +342,15 @@ impl MouseEvent {
-1
};

// https://w3c.github.io/pointerevents/#dfn-attributes-and-default-actions
// For pointerenter and pointerleave events, the composed [DOM] attribute SHOULD be false;
// for all other pointer events in the table above, the attribute SHOULD be true.
let composed = !matches!(&*event_type, "pointerenter" | "pointerleave");

let window = self.global();
let window = window.as_window();

PointerEvent::new(
let pointer_event = PointerEvent::new(
window,
event_type,
EventBubbles::from(self.upcast::<Event>().Bubbles()),
Expand Down Expand Up @@ -374,7 +380,11 @@ impl MouseEvent {
vec![], // coalesced_events
vec![], // predicted_events
can_gc,
)
);

pointer_event.upcast::<Event>().set_composed(composed);

pointer_event
}

/// Create a PointerEvent for hover events (pointerover, pointerenter, pointerout, pointerleave).
Expand Down Expand Up @@ -431,6 +441,12 @@ impl MouseEvent {
can_gc,
);

// https://w3c.github.io/pointerevents/#dfn-attributes-and-default-actions
// For pointerenter and pointerleave events, the composed [DOM] attribute SHOULD be false;
// for all other pointer events in the table above, the attribute SHOULD be true.
let composed = !matches!(event_type, "pointerenter" | "pointerleave");
pointer_event.upcast::<Event>().set_composed(composed);

// Set trusted to match the source mouse event
pointer_event
.upcast::<Event>()
Expand All @@ -441,7 +457,7 @@ impl MouseEvent {
}

impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
/// <https://w3c.github.io/uievents/#dom-mouseevent-mouseevent>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-constructor>
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
Expand Down Expand Up @@ -480,22 +496,22 @@ impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
Ok(event)
}

/// <https://w3c.github.io/uievents/#widl-MouseEvent-screenX>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-screenx>
fn ScreenX(&self) -> i32 {
self.screen_point.get().x
}

/// <https://w3c.github.io/uievents/#widl-MouseEvent-screenY>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-screeny>
fn ScreenY(&self) -> i32 {
self.screen_point.get().y
}

/// <https://w3c.github.io/uievents/#widl-MouseEvent-clientX>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-clientx>
fn ClientX(&self) -> i32 {
self.client_point.get().x
}

/// <https://w3c.github.io/uievents/#widl-MouseEvent-clientY>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-clienty>
fn ClientY(&self) -> i32 {
self.client_point.get().y
}
Expand Down Expand Up @@ -586,42 +602,42 @@ impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
self.PageY()
}

/// <https://w3c.github.io/uievents/#dom-mouseevent-ctrlkey>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-ctrlkey>
fn CtrlKey(&self) -> bool {
self.modifiers.get().contains(Modifiers::CONTROL)
}

/// <https://w3c.github.io/uievents/#dom-mouseevent-shiftkey>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-shiftkey>
fn ShiftKey(&self) -> bool {
self.modifiers.get().contains(Modifiers::SHIFT)
}

/// <https://w3c.github.io/uievents/#dom-mouseevent-altkey>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-altkey>
fn AltKey(&self) -> bool {
self.modifiers.get().contains(Modifiers::ALT)
}

/// <https://w3c.github.io/uievents/#dom-mouseevent-metakey>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-metakey>
fn MetaKey(&self) -> bool {
self.modifiers.get().contains(Modifiers::META)
}

/// <https://w3c.github.io/uievents/#dom-mouseevent-button>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-button>
fn Button(&self) -> i16 {
self.button.get()
}

/// <https://w3c.github.io/uievents/#dom-mouseevent-buttons>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-buttons>
fn Buttons(&self) -> u16 {
self.buttons.get()
}

/// <https://w3c.github.io/uievents/#widl-MouseEvent-relatedTarget>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-relatedtarget>
fn GetRelatedTarget(&self) -> Option<DomRoot<EventTarget>> {
self.upcast::<Event>().related_target()
}

/// <https://w3c.github.io/uievents/#widl-MouseEvent-initMouseEvent>
/// <https://w3c.github.io/pointerevents/#dom-mouseevent-initmouseevent>
fn InitMouseEvent(
&self,
type_arg: DOMString,
Expand Down Expand Up @@ -696,7 +712,7 @@ impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
self.uievent.IsTrusted()
}

/// <https://w3c.github.io/uievents/#dom-mouseevent-getmodifierstate>
/// <https://w3c.github.io/pointerevents/#dfn-getmodifierstate-keyarg>
fn GetModifierState(&self, key_arg: DOMString) -> bool {
self.modifiers
.get()
Expand Down
6 changes: 1 addition & 5 deletions components/script/dom/event/pointerevent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use dom_struct::dom_struct;
use euclid::Point2D;
use js::rust::HandleObject;
use keyboard_types::Modifiers;
use script_bindings::inheritance::Castable;
use style::Atom;
use style_traits::CSSPixel;

Expand All @@ -21,7 +20,7 @@ use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::event::{EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::mouseevent::MouseEvent;
use crate::dom::window::Window;
Expand Down Expand Up @@ -191,8 +190,6 @@ impl PointerEvent {
can_gc: CanGc,
) -> DomRoot<PointerEvent> {
let ev = PointerEvent::new_uninitialized_with_proto(window, proto, can_gc);
// See <https://w3c.github.io/pointerevents/#attributes-and-default-actions>
let composed = &*event_type != "pointerenter" && &*event_type != "pointerleave";
ev.mouseevent.initialize_mouse_event(
event_type,
can_bubble,
Expand All @@ -208,7 +205,6 @@ impl PointerEvent {
related_target,
point_in_target,
);
ev.mouseevent.upcast::<Event>().set_composed(composed);
ev.pointer_id.set(pointer_id);
ev.width.set(width);
ev.height.set(height);
Expand Down
15 changes: 12 additions & 3 deletions components/script/dom/touch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ use std::f64::consts::PI;

use dom_struct::dom_struct;
use euclid::Point2D;
use script_bindings::inheritance::Castable;

use crate::dom::bindings::codegen::Bindings::TouchBinding::TouchMethods;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::bindings::root::{DomRoot, MutDom};
use crate::dom::event::{EventBubbles, EventCancelable};
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::pointerevent::PointerEvent;
use crate::dom::window::Window;
Expand Down Expand Up @@ -138,7 +139,7 @@ impl Touch {
_ => (EventBubbles::Bubbles, EventCancelable::from(is_cancelable)),
};

PointerEvent::new(
let pointer_event = PointerEvent::new(
window,
event_type.into(),
bubbles,
Expand Down Expand Up @@ -168,7 +169,15 @@ impl Touch {
vec![], // coalesced_events
vec![], // predicted_events
can_gc,
)
);

// https://w3c.github.io/pointerevents/#dfn-attributes-and-default-actions
// For pointerenter and pointerleave events, the composed [DOM] attribute SHOULD be false;
// for all other pointer events in the table above, the attribute SHOULD be true.
let composed = !matches!(event_type, "pointerenter" | "pointerleave");
pointer_event.upcast::<Event>().set_composed(composed);
Comment on lines +174 to +178

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should happen in PointerEvent::new so that it is applied consistently?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only pointer events that are caused by a user action should be composed.

In other words, (new PointerEvent("pointerup").composed == false.

That's why the code needs to be here.

@simonwuelker simonwuelker Mar 31, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm actually the constructor does currently set the composed flag:

// See <https://w3c.github.io/pointerevents/#attributes-and-default-actions>
let composed = &*event_type != "pointerenter" && &*event_type != "pointerleave";

let's fix that...

@mrobinson mrobinson Mar 31, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we do for mouse events is have two constructors, one for system input events and one for synthetic events or constructed events.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm pointerevents are already (mostly) constructed in MouseEvent::to_pointer_event and MouseEvent::to_pointer_hover_event we can set the flags there.


pointer_event
}
}

Expand Down
6 changes: 3 additions & 3 deletions components/script_bindings/webidls/MouseEvent.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* 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/. */

// https://w3c.github.io/uievents/#interface-mouseevent
// https://w3c.github.io/pointerevents/#dom-mouseevent
[Exposed=Window]
interface MouseEvent : UIEvent {
[Throws] constructor(DOMString typeArg, optional MouseEventInit mouseEventInitDict = {});
Expand All @@ -27,7 +27,7 @@ interface MouseEvent : UIEvent {
boolean getModifierState (DOMString keyArg);
};

// https://w3c.github.io/uievents/#dictdef-eventmodifierinit
// https://w3c.github.io/pointerevents/#dom-mouseeventinit
dictionary MouseEventInit : EventModifierInit {
long screenX = 0;
long screenY = 0;
Expand All @@ -38,7 +38,7 @@ dictionary MouseEventInit : EventModifierInit {
EventTarget? relatedTarget = null;
};

// https://w3c.github.io/uievents/#idl-interface-MouseEvent-initializers
// https://w3c.github.io/pointerevents/#dom-mouseevent-initmouseevent
partial interface MouseEvent {
// Deprecated in DOM Level 3
undefined initMouseEvent (DOMString typeArg, boolean bubblesArg, boolean cancelableArg,
Expand Down
4 changes: 2 additions & 2 deletions components/script_bindings/webidls/WheelEvent.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* 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/. */

// https://w3c.github.io/uievents/#interface-wheelevent
// https://w3c.github.io/pointerevents/#interface-wheelevent
[Exposed=Window]
interface WheelEvent : MouseEvent {
[Throws] constructor(DOMString typeArg, optional WheelEventInit wheelEventInitDict = {});
Expand All @@ -15,7 +15,7 @@ interface WheelEvent : MouseEvent {
readonly attribute unsigned long deltaMode;
};

// https://w3c.github.io/uievents/#idl-wheeleventinit
// https://w3c.github.io/pointerevents/#dom-wheeleventinit
dictionary WheelEventInit : MouseEventInit {
double deltaX = 0.0;
double deltaY = 0.0;
Expand Down
Loading
Loading