mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
dom: Track "removed" event listener status (#36163)
The DOM event listener "removed" status should be supported to track the following situations (with immediate effect of listener removal): - Removing a later event listener while an earlier listener for the same event is running - Nested usage (recursively dispatch another event) of "once" listeners https://dom.spec.whatwg.org/#event-listener-removed During event dispatching requires to clone event listeners list on "invoke" step https://dom.spec.whatwg.org/#concept-event-listener-invoke and the lowercase "event listener" concept in Servo is EventListenerEntry https://dom.spec.whatwg.org/#concept-event-listener Bug: #25479, #25090 Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
This commit is contained in:
parent
ed3dd8fbe0
commit
5f5bf87eee
4 changed files with 106 additions and 105 deletions
|
@ -29,7 +29,7 @@ use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_w
|
|||
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::element::Element;
|
||||
use crate::dom::eventtarget::{CompiledEventListener, EventTarget, ListenerPhase};
|
||||
use crate::dom::eventtarget::{EventListeners, EventTarget, ListenerPhase};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::htmlinputelement::InputActivationState;
|
||||
use crate::dom::htmlslotelement::HTMLSlotElement;
|
||||
|
@ -1151,10 +1151,7 @@ fn invoke(
|
|||
event.current_target.set(Some(&segment.invocation_target));
|
||||
|
||||
// Step 6. Let listeners be a clone of event’s currentTarget attribute value’s event listener list.
|
||||
let listeners =
|
||||
segment
|
||||
.invocation_target
|
||||
.get_listeners_for(&event.type_(), Some(phase), can_gc);
|
||||
let listeners = segment.invocation_target.get_listeners_for(&event.type_());
|
||||
|
||||
// Step 7. Let invocationTargetInShadowTree be struct’s invocation-target-in-shadow-tree.
|
||||
let invocation_target_in_shadow_tree = segment.invocation_target_in_shadow_tree;
|
||||
|
@ -1207,8 +1204,8 @@ fn invoke(
|
|||
/// <https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke>
|
||||
fn inner_invoke(
|
||||
event: &Event,
|
||||
listeners: &[CompiledEventListener],
|
||||
_phase: ListenerPhase,
|
||||
listeners: &EventListeners,
|
||||
phase: ListenerPhase,
|
||||
invocation_target_in_shadow_tree: bool,
|
||||
timeline_window: Option<&Window>,
|
||||
can_gc: CanGc,
|
||||
|
@ -1217,25 +1214,21 @@ fn inner_invoke(
|
|||
let mut found = false;
|
||||
|
||||
// Step 2. For each listener in listeners, whose removed is false:
|
||||
for listener in listeners {
|
||||
// FIXME(#25479): We need an "if !listener.removed()" here,
|
||||
// but there's a subtlety. Where Servo is currently using the
|
||||
// CompiledEventListener, we really need something that maps to
|
||||
// https://dom.spec.whatwg.org/#concept-event-listener
|
||||
// which is not the same thing as the EventListener interface.
|
||||
// script::dom::eventtarget::EventListenerEntry is the closest
|
||||
// match we have, and is already holding the "once" flag,
|
||||
// but it's not a drop-in replacement.
|
||||
for listener in listeners.iter() {
|
||||
if listener.borrow().removed() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Steps 2.1 and 2.3-2.4 are not done because `listeners` contain only the
|
||||
// relevant ones for this invoke call during the dispatch algorithm.
|
||||
// TODO: Step 2.1 If event’s type attribute value is not listener’s type, then continue.
|
||||
// Step 2.1 If event’s type attribute value is not listener’s type, then continue.
|
||||
|
||||
// Step 2.2. Set found to true.
|
||||
found = true;
|
||||
|
||||
// TODO Step 2.3 If phase is "capturing" and listener’s capture is false, then continue.
|
||||
// TODO Step 2.4 If phase is "bubbling" and listener’s capture is true, then continue.
|
||||
// Step 2.3 If phase is "capturing" and listener’s capture is false, then continue.
|
||||
// Step 2.4 If phase is "bubbling" and listener’s capture is true, then continue.
|
||||
if listener.borrow().phase() != phase {
|
||||
continue;
|
||||
}
|
||||
|
||||
let event_target = event
|
||||
.GetCurrentTarget()
|
||||
|
@ -1243,12 +1236,20 @@ fn inner_invoke(
|
|||
|
||||
// Step 2.5 If listener’s once is true, then remove an event listener given event’s currentTarget
|
||||
// attribute value and listener.
|
||||
if let CompiledEventListener::Listener(event_listener) = listener {
|
||||
event_target.remove_listener_if_once(&event.type_(), event_listener);
|
||||
if listener.borrow().once() {
|
||||
event_target.remove_listener(&event.type_(), listener);
|
||||
}
|
||||
|
||||
let Some(compiled_listener) =
|
||||
listener
|
||||
.borrow()
|
||||
.get_compiled_listener(&event_target, &event.type_(), can_gc)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Step 2.6 Let global be listener callback’s associated realm’s global object.
|
||||
let global = listener.associated_global();
|
||||
let global = compiled_listener.associated_global();
|
||||
|
||||
// Step 2.7 Let currentEvent be undefined.
|
||||
let mut current_event = None;
|
||||
|
@ -1264,18 +1265,22 @@ fn inner_invoke(
|
|||
}
|
||||
|
||||
// Step 2.9 If listener’s 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));
|
||||
}
|
||||
event.set_in_passive_listener(event_target.is_passive(&event.type_(), listener));
|
||||
|
||||
// Step 2.10 If global is a Window object, then record timing info for event listener given event and 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 object’s operation with listener’s callback, "handleEvent", « event »,
|
||||
// and event’s currentTarget attribute value. If this throws an exception exception:
|
||||
// Step 2.10.1 Report exception for listener’s callback’s corresponding JavaScript object’s
|
||||
// associated realm’s global object.
|
||||
// TODO Step 2.10.2 Set legacyOutputDidListenersThrowFlag if given.
|
||||
let marker = TimelineMarker::start("DOMEvent".to_owned());
|
||||
listener.call_or_handle_event(&event_target, event, ExceptionHandling::Report, can_gc);
|
||||
compiled_listener.call_or_handle_event(
|
||||
&event_target,
|
||||
event,
|
||||
ExceptionHandling::Report,
|
||||
can_gc,
|
||||
);
|
||||
if let Some(window) = timeline_window {
|
||||
window.emit_timeline_marker(marker.end());
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue