script: use passive event listener option on AddEventListenerOptions (#35877)

Signed-off-by: Shane Handley <shanehandley@fastmail.com>
This commit is contained in:
shanehandley 2025-03-10 20:44:16 +11:00 committed by GitHub
parent 1b6b21cb85
commit 7fc5dc5c69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 87 additions and 217 deletions

View file

@ -95,6 +95,9 @@ pub(crate) struct Event {
/// <https://dom.spec.whatwg.org/#event-relatedtarget>
related_target: MutNullableDom<EventTarget>,
/// <https://dom.spec.whatwg.org/#in-passive-listener-flag>
in_passive_listener: Cell<bool>,
}
/// An element on an [event path](https://dom.spec.whatwg.org/#event-path)
@ -140,6 +143,7 @@ impl Event {
time_stamp: CrossProcessInstant::now(),
path: DomRefCell::default(),
related_target: Default::default(),
in_passive_listener: Cell::new(false),
}
}
@ -217,6 +221,10 @@ impl Event {
self.target.set(target_);
}
pub(crate) fn set_in_passive_listener(&self, value: bool) {
self.in_passive_listener.set(value);
}
/// <https://dom.spec.whatwg.org/#concept-event-path-append>
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn append_to_path(
@ -728,6 +736,13 @@ impl Event {
&*target_node.GetRootNode(&GetRootNodeOptions::empty()) != shadow_root.upcast::<Node>()
}
/// <https://dom.spec.whatwg.org/#set-the-canceled-flag>
fn set_the_cancelled_flag(&self) {
if self.cancelable.get() && !self.in_passive_listener.get() {
self.canceled.set(EventDefault::Prevented)
}
}
}
impl EventMethods<crate::DomTypeHolder> for Event {
@ -917,9 +932,7 @@ impl EventMethods<crate::DomTypeHolder> for Event {
/// <https://dom.spec.whatwg.org/#dom-event-preventdefault>
fn PreventDefault(&self) {
if self.cancelable.get() {
self.canceled.set(EventDefault::Prevented)
}
self.set_the_cancelled_flag();
}
/// <https://dom.spec.whatwg.org/#dom-event-stoppropagation>
@ -951,7 +964,7 @@ impl EventMethods<crate::DomTypeHolder> for Event {
/// <https://dom.spec.whatwg.org/#dom-event-returnvalue>
fn SetReturnValue(&self, val: bool) {
if !val {
self.PreventDefault();
self.set_the_cancelled_flag();
}
}
@ -1224,13 +1237,14 @@ fn inner_invoke(
// TODO Step 2.3 If phase is "capturing" and listeners capture is false, then continue.
// TODO Step 2.4 If phase is "bubbling" and listeners capture is true, then continue.
let event_target = event
.GetCurrentTarget()
.expect("event target was initialized as part of \"invoke\"");
// Step 2.5 If listeners once is true, then remove an event listener given events currentTarget
// attribute value and listener.
if let CompiledEventListener::Listener(event_listener) = listener {
event
.GetCurrentTarget()
.expect("event target was initialized as part of \"invoke\"")
.remove_listener_if_once(&event.type_(), event_listener);
event_target.remove_listener_if_once(&event.type_(), event_listener);
}
// Step 2.6 Let global be listener callbacks associated realms global object.
@ -1249,7 +1263,10 @@ fn inner_invoke(
}
}
// TODO Step 2.9 If listeners passive is true, then set events in passive listener flag.
// Step 2.9 If listeners passive is true, then set event's in passive listener flag.
if let CompiledEventListener::Listener(event_listener) = listener {
event.set_in_passive_listener(event_target.is_passive(&event.type_(), event_listener));
}
// Step 2.10 If global is a Window object, then record timing info for event listener given event and listener.
// Step 2.11 Call a user objects operation with listeners callback, "handleEvent", « event »,
@ -1258,19 +1275,13 @@ fn inner_invoke(
// associated realms global object.
// TODO Step 2.10.2 Set legacyOutputDidListenersThrowFlag if given.
let marker = TimelineMarker::start("DOMEvent".to_owned());
listener.call_or_handle_event(
&event
.GetCurrentTarget()
.expect("event target was initialized as part of \"invoke\""),
event,
ExceptionHandling::Report,
can_gc,
);
listener.call_or_handle_event(&event_target, event, ExceptionHandling::Report, can_gc);
if let Some(window) = timeline_window {
window.emit_timeline_marker(marker.end());
}
// TODO Step 2.12 Unset events in passive listener flag.
// Step 2.12 Unset events in passive listener flag.
event.set_in_passive_listener(false);
// Step 2.13 If global is a Window object, then set globals current event to currentEvent.
if let Some(window) = global.downcast::<Window>() {

View file

@ -23,6 +23,7 @@ use js::rust::{
use libc::c_char;
use servo_atoms::Atom;
use servo_url::ServoUrl;
use style::str::HTML_SPACE_CHARACTERS;
use crate::dom::beforeunloadevent::BeforeUnloadEvent;
use crate::dom::bindings::callback::{CallbackContainer, CallbackFunction, ExceptionHandling};
@ -41,6 +42,7 @@ use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::GenericBindings::DocumentBinding::Document_Binding::DocumentMethods;
use crate::dom::bindings::codegen::UnionTypes::{
AddEventListenerOptionsOrBoolean, EventListenerOptionsOrBoolean, EventOrString,
};
@ -308,6 +310,7 @@ struct EventListenerEntry {
phase: ListenerPhase,
listener: EventListenerType,
once: bool,
passive: Option<bool>,
}
impl std::cmp::PartialEq for EventListenerEntry {
@ -438,6 +441,42 @@ impl EventTarget {
*self.handlers.borrow_mut() = Default::default();
}
/// <https://dom.spec.whatwg.org/#default-passive-value>
fn default_passive_value(&self, ty: &Atom) -> bool {
// Return true if all of the following are true:
let event_type = ty.to_ascii_lowercase();
// type is one of "touchstart", "touchmove", "wheel", or "mousewheel"
let matches_event_type = matches!(
event_type.trim_matches(HTML_SPACE_CHARACTERS),
"touchstart" | "touchmove" | "wheel" | "mousewheel"
);
if !matches_event_type {
return false;
}
// eventTarget is a Window object
if self.is::<Window>() {
return true;
}
// or ...
if let Some(node) = self.downcast::<Node>() {
let node_document = node.owner_document();
let event_target = self.upcast::<EventTarget>();
// is a node whose node document is eventTarget
return event_target == node_document.upcast::<EventTarget>()
// or is a node whose node documents document element is eventTarget
|| node_document.GetDocumentElement().is_some_and(|n| n.upcast::<EventTarget>() == event_target)
// or is a node whose node documents body element is eventTarget
|| node_document.GetBody().is_some_and(|n| n.upcast::<EventTarget>() == event_target);
}
false
}
/// <https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handlers-11>
fn set_inline_event_listener(&self, ty: Atom, listener: Option<InlineEventListener>) {
let mut handlers = self.handlers.borrow_mut();
@ -467,6 +506,7 @@ impl EventTarget {
phase: ListenerPhase::Bubbling,
listener: EventListenerType::Inline(listener.into()),
once: false,
passive: None,
});
}
},
@ -482,6 +522,17 @@ impl EventTarget {
}
}
/// Determines the `passive` attribute of an associated event listener
pub(crate) fn is_passive(&self, ty: &Atom, listener: &Rc<EventListener>) -> bool {
let handlers = self.handlers.borrow();
let listener_instance = EventListenerType::Additive(listener.clone());
handlers
.get(ty)
.and_then(|entries| entries.iter().find(|e| e.listener == listener_instance))
.is_some_and(|entry| entry.passive.unwrap_or(self.default_passive_value(ty)))
}
fn get_inline_event_listener(&self, ty: &Atom, can_gc: CanGc) -> Option<CommonEventHandler> {
let handlers = self.handlers.borrow();
handlers
@ -745,7 +796,8 @@ impl EventTarget {
event.fire(self, can_gc);
event
}
// https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener
/// <https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener>
pub(crate) fn add_event_listener(
&self,
ty: DOMString,
@ -771,6 +823,7 @@ impl EventTarget {
phase,
listener: EventListenerType::Additive(listener),
once: options.once,
passive: options.passive,
};
if !entry.contains(&new_entry) {
entry.push(new_entry);
@ -799,6 +852,7 @@ impl EventTarget {
phase,
listener: EventListenerType::Additive(listener.clone()),
once: false,
passive: None,
};
if let Some(position) = entry.iter().position(|e| *e == old_entry) {
entry.remove(position);
@ -929,6 +983,7 @@ impl From<AddEventListenerOptionsOrBoolean> for AddEventListenerOptions {
AddEventListenerOptionsOrBoolean::Boolean(capture) => Self {
parent: EventListenerOptions { capture },
once: false,
passive: None,
},
}
}

View file

@ -106,6 +106,7 @@ impl MediaQueryListMethods<crate::DomTypeHolder> for MediaQueryList {
AddEventListenerOptions {
parent: EventListenerOptions { capture: false },
once: false,
passive: None,
},
);
}

View file

@ -29,6 +29,6 @@ dictionary EventListenerOptions {
};
dictionary AddEventListenerOptions : EventListenerOptions {
// boolean passive = false;
boolean passive;
boolean once = false;
};

View file

@ -1,26 +0,0 @@
[AddEventListenerOptions-passive.any.worker.html]
[Supports passive option on addEventListener only]
expected: FAIL
[preventDefault should be ignored if-and-only-if the passive option is true]
expected: FAIL
[returnValue should be ignored if-and-only-if the passive option is true]
expected: FAIL
[passive behavior of one listener should be unaffected by the presence of other listeners]
expected: FAIL
[AddEventListenerOptions-passive.any.html]
[Supports passive option on addEventListener only]
expected: FAIL
[preventDefault should be ignored if-and-only-if the passive option is true]
expected: FAIL
[returnValue should be ignored if-and-only-if the passive option is true]
expected: FAIL
[passive behavior of one listener should be unaffected by the presence of other listeners]
expected: FAIL

View file

@ -1,171 +0,0 @@
[passive-by-default.html]
[touchstart listener is passive by default for Window]
expected: FAIL
[touchstart listener is passive with {passive:undefined} for Window]
expected: FAIL
[touchstart listener is passive with {passive:true} for Window]
expected: FAIL
[touchstart listener is passive by default for Document]
expected: FAIL
[touchstart listener is passive with {passive:undefined} for Document]
expected: FAIL
[touchstart listener is passive with {passive:true} for Document]
expected: FAIL
[touchstart listener is passive by default for HTMLHtmlElement]
expected: FAIL
[touchstart listener is passive with {passive:undefined} for HTMLHtmlElement]
expected: FAIL
[touchstart listener is passive with {passive:true} for HTMLHtmlElement]
expected: FAIL
[touchstart listener is passive by default for HTMLBodyElement]
expected: FAIL
[touchstart listener is passive with {passive:undefined} for HTMLBodyElement]
expected: FAIL
[touchstart listener is passive with {passive:true} for HTMLBodyElement]
expected: FAIL
[touchstart listener is passive with {passive:true} for HTMLDivElement]
expected: FAIL
[touchmove listener is passive by default for Window]
expected: FAIL
[touchmove listener is passive with {passive:undefined} for Window]
expected: FAIL
[touchmove listener is passive with {passive:true} for Window]
expected: FAIL
[touchmove listener is passive by default for Document]
expected: FAIL
[touchmove listener is passive with {passive:undefined} for Document]
expected: FAIL
[touchmove listener is passive with {passive:true} for Document]
expected: FAIL
[touchmove listener is passive by default for HTMLHtmlElement]
expected: FAIL
[touchmove listener is passive with {passive:undefined} for HTMLHtmlElement]
expected: FAIL
[touchmove listener is passive with {passive:true} for HTMLHtmlElement]
expected: FAIL
[touchmove listener is passive by default for HTMLBodyElement]
expected: FAIL
[touchmove listener is passive with {passive:undefined} for HTMLBodyElement]
expected: FAIL
[touchmove listener is passive with {passive:true} for HTMLBodyElement]
expected: FAIL
[touchmove listener is passive with {passive:true} for HTMLDivElement]
expected: FAIL
[wheel listener is passive by default for Window]
expected: FAIL
[wheel listener is passive with {passive:undefined} for Window]
expected: FAIL
[wheel listener is passive with {passive:true} for Window]
expected: FAIL
[wheel listener is passive by default for Document]
expected: FAIL
[wheel listener is passive with {passive:undefined} for Document]
expected: FAIL
[wheel listener is passive with {passive:true} for Document]
expected: FAIL
[wheel listener is passive by default for HTMLHtmlElement]
expected: FAIL
[wheel listener is passive with {passive:undefined} for HTMLHtmlElement]
expected: FAIL
[wheel listener is passive with {passive:true} for HTMLHtmlElement]
expected: FAIL
[wheel listener is passive by default for HTMLBodyElement]
expected: FAIL
[wheel listener is passive with {passive:undefined} for HTMLBodyElement]
expected: FAIL
[wheel listener is passive with {passive:true} for HTMLBodyElement]
expected: FAIL
[wheel listener is passive with {passive:true} for HTMLDivElement]
expected: FAIL
[mousewheel listener is passive by default for Window]
expected: FAIL
[mousewheel listener is passive with {passive:undefined} for Window]
expected: FAIL
[mousewheel listener is passive with {passive:true} for Window]
expected: FAIL
[mousewheel listener is passive by default for Document]
expected: FAIL
[mousewheel listener is passive with {passive:undefined} for Document]
expected: FAIL
[mousewheel listener is passive with {passive:true} for Document]
expected: FAIL
[mousewheel listener is passive by default for HTMLHtmlElement]
expected: FAIL
[mousewheel listener is passive with {passive:undefined} for HTMLHtmlElement]
expected: FAIL
[mousewheel listener is passive with {passive:true} for HTMLHtmlElement]
expected: FAIL
[mousewheel listener is passive by default for HTMLBodyElement]
expected: FAIL
[mousewheel listener is passive with {passive:undefined} for HTMLBodyElement]
expected: FAIL
[mousewheel listener is passive with {passive:true} for HTMLBodyElement]
expected: FAIL
[mousewheel listener is passive with {passive:true} for HTMLDivElement]
expected: FAIL
[touchend listener is passive with {passive:true} for Window]
expected: FAIL
[touchend listener is passive with {passive:true} for Document]
expected: FAIL
[touchend listener is passive with {passive:true} for HTMLHtmlElement]
expected: FAIL
[touchend listener is passive with {passive:true} for HTMLBodyElement]
expected: FAIL
[touchend listener is passive with {passive:true} for HTMLDivElement]
expected: FAIL