mirror of
https://github.com/servo/servo.git
synced 2025-08-04 05:00:08 +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::root::{Dom, DomRoot, MutNullableDom};
|
||||||
use crate::dom::bindings::str::DOMString;
|
use crate::dom::bindings::str::DOMString;
|
||||||
use crate::dom::element::Element;
|
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::globalscope::GlobalScope;
|
||||||
use crate::dom::htmlinputelement::InputActivationState;
|
use crate::dom::htmlinputelement::InputActivationState;
|
||||||
use crate::dom::htmlslotelement::HTMLSlotElement;
|
use crate::dom::htmlslotelement::HTMLSlotElement;
|
||||||
|
@ -1151,10 +1151,7 @@ fn invoke(
|
||||||
event.current_target.set(Some(&segment.invocation_target));
|
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.
|
// Step 6. Let listeners be a clone of event’s currentTarget attribute value’s event listener list.
|
||||||
let listeners =
|
let listeners = segment.invocation_target.get_listeners_for(&event.type_());
|
||||||
segment
|
|
||||||
.invocation_target
|
|
||||||
.get_listeners_for(&event.type_(), Some(phase), can_gc);
|
|
||||||
|
|
||||||
// Step 7. Let invocationTargetInShadowTree be struct’s invocation-target-in-shadow-tree.
|
// Step 7. Let invocationTargetInShadowTree be struct’s invocation-target-in-shadow-tree.
|
||||||
let invocation_target_in_shadow_tree = segment.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>
|
/// <https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke>
|
||||||
fn inner_invoke(
|
fn inner_invoke(
|
||||||
event: &Event,
|
event: &Event,
|
||||||
listeners: &[CompiledEventListener],
|
listeners: &EventListeners,
|
||||||
_phase: ListenerPhase,
|
phase: ListenerPhase,
|
||||||
invocation_target_in_shadow_tree: bool,
|
invocation_target_in_shadow_tree: bool,
|
||||||
timeline_window: Option<&Window>,
|
timeline_window: Option<&Window>,
|
||||||
can_gc: CanGc,
|
can_gc: CanGc,
|
||||||
|
@ -1217,25 +1214,21 @@ fn inner_invoke(
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
|
|
||||||
// Step 2. For each listener in listeners, whose removed is false:
|
// Step 2. For each listener in listeners, whose removed is false:
|
||||||
for listener in listeners {
|
for listener in listeners.iter() {
|
||||||
// FIXME(#25479): We need an "if !listener.removed()" here,
|
if listener.borrow().removed() {
|
||||||
// but there's a subtlety. Where Servo is currently using the
|
continue;
|
||||||
// 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.
|
|
||||||
|
|
||||||
// Steps 2.1 and 2.3-2.4 are not done because `listeners` contain only the
|
// Step 2.1 If event’s type attribute value is not listener’s type, then continue.
|
||||||
// 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.2. Set found to true.
|
// Step 2.2. Set found to true.
|
||||||
found = true;
|
found = true;
|
||||||
|
|
||||||
// TODO Step 2.3 If phase is "capturing" and listener’s capture is false, then continue.
|
// 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.4 If phase is "bubbling" and listener’s capture is true, then continue.
|
||||||
|
if listener.borrow().phase() != phase {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let event_target = event
|
let event_target = event
|
||||||
.GetCurrentTarget()
|
.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
|
// Step 2.5 If listener’s once is true, then remove an event listener given event’s currentTarget
|
||||||
// attribute value and listener.
|
// attribute value and listener.
|
||||||
if let CompiledEventListener::Listener(event_listener) = listener {
|
if listener.borrow().once() {
|
||||||
event_target.remove_listener_if_once(&event.type_(), event_listener);
|
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.
|
// 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.
|
// Step 2.7 Let currentEvent be undefined.
|
||||||
let mut current_event = None;
|
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.
|
// 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_(), 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.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 »,
|
// 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:
|
// 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
|
// Step 2.10.1 Report exception for listener’s callback’s corresponding JavaScript object’s
|
||||||
// associated realm’s global object.
|
// associated realm’s global object.
|
||||||
// TODO Step 2.10.2 Set legacyOutputDidListenersThrowFlag if given.
|
// TODO Step 2.10.2 Set legacyOutputDidListenersThrowFlag if given.
|
||||||
let marker = TimelineMarker::start("DOMEvent".to_owned());
|
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 {
|
if let Some(window) = timeline_window {
|
||||||
window.emit_timeline_marker(marker.end());
|
window.emit_timeline_marker(marker.end());
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,11 +307,36 @@ impl CompiledEventListener {
|
||||||
// (as distinct from https://dom.spec.whatwg.org/#callbackdef-eventlistener)
|
// (as distinct from https://dom.spec.whatwg.org/#callbackdef-eventlistener)
|
||||||
#[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf)]
|
#[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf)]
|
||||||
/// A listener in a collection of event listeners.
|
/// A listener in a collection of event listeners.
|
||||||
struct EventListenerEntry {
|
pub(crate) struct EventListenerEntry {
|
||||||
phase: ListenerPhase,
|
phase: ListenerPhase,
|
||||||
listener: EventListenerType,
|
listener: EventListenerType,
|
||||||
once: bool,
|
once: bool,
|
||||||
passive: Option<bool>,
|
passive: Option<bool>,
|
||||||
|
removed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventListenerEntry {
|
||||||
|
pub(crate) fn phase(&self) -> ListenerPhase {
|
||||||
|
self.phase
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn once(&self) -> bool {
|
||||||
|
self.once
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn removed(&self) -> bool {
|
||||||
|
self.removed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler>
|
||||||
|
pub(crate) fn get_compiled_listener(
|
||||||
|
&self,
|
||||||
|
owner: &EventTarget,
|
||||||
|
ty: &Atom,
|
||||||
|
can_gc: CanGc,
|
||||||
|
) -> Option<CompiledEventListener> {
|
||||||
|
self.listener.get_compiled_listener(owner, ty, can_gc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::cmp::PartialEq for EventListenerEntry {
|
impl std::cmp::PartialEq for EventListenerEntry {
|
||||||
|
@ -320,19 +345,21 @@ impl std::cmp::PartialEq for EventListenerEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(JSTraceable, MallocSizeOf)]
|
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||||
/// A mix of potentially uncompiled and compiled event listeners of the same type.
|
/// A mix of potentially uncompiled and compiled event listeners of the same type.
|
||||||
struct EventListeners(Vec<EventListenerEntry>);
|
pub(crate) struct EventListeners(
|
||||||
|
#[ignore_malloc_size_of = "Rc"] Vec<Rc<RefCell<EventListenerEntry>>>,
|
||||||
|
);
|
||||||
|
|
||||||
impl Deref for EventListeners {
|
impl Deref for EventListeners {
|
||||||
type Target = Vec<EventListenerEntry>;
|
type Target = Vec<Rc<RefCell<EventListenerEntry>>>;
|
||||||
fn deref(&self) -> &Vec<EventListenerEntry> {
|
fn deref(&self) -> &Vec<Rc<RefCell<EventListenerEntry>>> {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for EventListeners {
|
impl DerefMut for EventListeners {
|
||||||
fn deref_mut(&mut self) -> &mut Vec<EventListenerEntry> {
|
fn deref_mut(&mut self) -> &mut Vec<Rc<RefCell<EventListenerEntry>>> {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,7 +373,7 @@ impl EventListeners {
|
||||||
can_gc: CanGc,
|
can_gc: CanGc,
|
||||||
) -> Option<CommonEventHandler> {
|
) -> Option<CommonEventHandler> {
|
||||||
for entry in &self.0 {
|
for entry in &self.0 {
|
||||||
if let EventListenerType::Inline(ref inline) = entry.listener {
|
if let EventListenerType::Inline(ref inline) = entry.borrow().listener {
|
||||||
// Step 1.1-1.8 and Step 2
|
// Step 1.1-1.8 and Step 2
|
||||||
return get_compiled_handler(inline, owner, ty, can_gc);
|
return get_compiled_handler(inline, owner, ty, can_gc);
|
||||||
}
|
}
|
||||||
|
@ -356,30 +383,7 @@ impl EventListeners {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler
|
|
||||||
fn get_listeners(
|
|
||||||
&self,
|
|
||||||
phase: Option<ListenerPhase>,
|
|
||||||
owner: &EventTarget,
|
|
||||||
ty: &Atom,
|
|
||||||
can_gc: CanGc,
|
|
||||||
) -> Vec<CompiledEventListener> {
|
|
||||||
self.0
|
|
||||||
.iter()
|
|
||||||
.filter_map(|entry| {
|
|
||||||
if phase.is_none() || Some(entry.phase) == phase {
|
|
||||||
// Step 1.1-1.8, 2
|
|
||||||
entry.listener.get_compiled_listener(owner, ty, can_gc)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_listeners(&self) -> bool {
|
fn has_listeners(&self) -> bool {
|
||||||
// TODO: add, and take into account, a 'removed' field?
|
|
||||||
// https://dom.spec.whatwg.org/#event-listener-removed
|
|
||||||
!self.0.is_empty()
|
!self.0.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -420,18 +424,11 @@ impl EventTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_listeners_for(
|
pub(crate) fn get_listeners_for(&self, type_: &Atom) -> EventListeners {
|
||||||
&self,
|
|
||||||
type_: &Atom,
|
|
||||||
specific_phase: Option<ListenerPhase>,
|
|
||||||
can_gc: CanGc,
|
|
||||||
) -> Vec<CompiledEventListener> {
|
|
||||||
self.handlers
|
self.handlers
|
||||||
.borrow()
|
.borrow()
|
||||||
.get(type_)
|
.get(type_)
|
||||||
.map_or(vec![], |listeners| {
|
.map_or(EventListeners(vec![]), |listeners| listeners.clone())
|
||||||
listeners.get_listeners(specific_phase, self, type_, can_gc)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn dispatch_event(&self, event: &Event, can_gc: CanGc) -> EventStatus {
|
pub(crate) fn dispatch_event(&self, event: &Event, can_gc: CanGc) -> EventStatus {
|
||||||
|
@ -439,7 +436,14 @@ impl EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remove_all_listeners(&self) {
|
pub(crate) fn remove_all_listeners(&self) {
|
||||||
*self.handlers.borrow_mut() = Default::default();
|
let mut handlers = self.handlers.borrow_mut();
|
||||||
|
for (_, entries) in handlers.iter() {
|
||||||
|
entries
|
||||||
|
.iter()
|
||||||
|
.for_each(|entry| entry.borrow_mut().removed = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
*handlers = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://dom.spec.whatwg.org/#default-passive-value>
|
/// <https://dom.spec.whatwg.org/#default-passive-value>
|
||||||
|
@ -488,50 +492,49 @@ impl EventTarget {
|
||||||
|
|
||||||
let idx = entries
|
let idx = entries
|
||||||
.iter()
|
.iter()
|
||||||
.position(|entry| matches!(entry.listener, EventListenerType::Inline(_)));
|
.position(|entry| matches!(entry.borrow().listener, EventListenerType::Inline(_)));
|
||||||
|
|
||||||
match idx {
|
match idx {
|
||||||
Some(idx) => match listener {
|
Some(idx) => match listener {
|
||||||
// Replace if there's something to replace with,
|
// Replace if there's something to replace with,
|
||||||
// but remove entirely if there isn't.
|
// but remove entirely if there isn't.
|
||||||
Some(listener) => {
|
Some(listener) => {
|
||||||
entries[idx].listener = EventListenerType::Inline(listener.into());
|
entries[idx].borrow_mut().listener = EventListenerType::Inline(listener.into());
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
entries.remove(idx);
|
entries.remove(idx).borrow_mut().removed = true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
if let Some(listener) = listener {
|
if let Some(listener) = listener {
|
||||||
entries.push(EventListenerEntry {
|
entries.push(Rc::new(RefCell::new(EventListenerEntry {
|
||||||
phase: ListenerPhase::Bubbling,
|
phase: ListenerPhase::Bubbling,
|
||||||
listener: EventListenerType::Inline(listener.into()),
|
listener: EventListenerType::Inline(listener.into()),
|
||||||
once: false,
|
once: false,
|
||||||
passive: None,
|
passive: None,
|
||||||
});
|
removed: false,
|
||||||
|
})));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remove_listener_if_once(&self, ty: &Atom, listener: &Rc<EventListener>) {
|
pub(crate) fn remove_listener(&self, ty: &Atom, entry: &Rc<RefCell<EventListenerEntry>>) {
|
||||||
let mut handlers = self.handlers.borrow_mut();
|
let mut handlers = self.handlers.borrow_mut();
|
||||||
|
|
||||||
let listener = EventListenerType::Additive(listener.clone());
|
|
||||||
if let Some(entries) = handlers.get_mut(ty) {
|
if let Some(entries) = handlers.get_mut(ty) {
|
||||||
entries.retain(|e| e.listener != listener || !e.once)
|
if let Some(position) = entries.iter().position(|e| *e == *entry) {
|
||||||
|
entries.remove(position).borrow_mut().removed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the `passive` attribute of an associated event listener
|
/// Determines the `passive` attribute of an associated event listener
|
||||||
pub(crate) fn is_passive(&self, ty: &Atom, listener: &Rc<EventListener>) -> bool {
|
pub(crate) fn is_passive(&self, ty: &Atom, listener: &Rc<RefCell<EventListenerEntry>>) -> bool {
|
||||||
let handlers = self.handlers.borrow();
|
listener
|
||||||
let listener_instance = EventListenerType::Additive(listener.clone());
|
.borrow()
|
||||||
|
.passive
|
||||||
handlers
|
.unwrap_or(self.default_passive_value(ty))
|
||||||
.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> {
|
fn get_inline_event_listener(&self, ty: &Atom, can_gc: CanGc) -> Option<CommonEventHandler> {
|
||||||
|
@ -810,7 +813,7 @@ impl EventTarget {
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
let mut handlers = self.handlers.borrow_mut();
|
let mut handlers = self.handlers.borrow_mut();
|
||||||
let entry = match handlers.entry(Atom::from(ty)) {
|
let entries = match handlers.entry(Atom::from(ty)) {
|
||||||
Occupied(entry) => entry.into_mut(),
|
Occupied(entry) => entry.into_mut(),
|
||||||
Vacant(entry) => entry.insert(EventListeners(vec![])),
|
Vacant(entry) => entry.insert(EventListeners(vec![])),
|
||||||
};
|
};
|
||||||
|
@ -820,14 +823,16 @@ impl EventTarget {
|
||||||
} else {
|
} else {
|
||||||
ListenerPhase::Bubbling
|
ListenerPhase::Bubbling
|
||||||
};
|
};
|
||||||
let new_entry = EventListenerEntry {
|
let new_entry = Rc::new(RefCell::new(EventListenerEntry {
|
||||||
phase,
|
phase,
|
||||||
listener: EventListenerType::Additive(listener),
|
listener: EventListenerType::Additive(listener),
|
||||||
once: options.once,
|
once: options.once,
|
||||||
passive: options.passive,
|
passive: options.passive,
|
||||||
};
|
removed: false,
|
||||||
if !entry.contains(&new_entry) {
|
}));
|
||||||
entry.push(new_entry);
|
|
||||||
|
if !entries.contains(&new_entry) {
|
||||||
|
entries.push(new_entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -842,21 +847,21 @@ impl EventTarget {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut handlers = self.handlers.borrow_mut();
|
let mut handlers = self.handlers.borrow_mut();
|
||||||
let entry = handlers.get_mut(&Atom::from(ty));
|
if let Some(entries) = handlers.get_mut(&Atom::from(ty)) {
|
||||||
if let Some(entry) = entry {
|
|
||||||
let phase = if options.capture {
|
let phase = if options.capture {
|
||||||
ListenerPhase::Capturing
|
ListenerPhase::Capturing
|
||||||
} else {
|
} else {
|
||||||
ListenerPhase::Bubbling
|
ListenerPhase::Bubbling
|
||||||
};
|
};
|
||||||
let old_entry = EventListenerEntry {
|
let old_entry = Rc::new(RefCell::new(EventListenerEntry {
|
||||||
phase,
|
phase,
|
||||||
listener: EventListenerType::Additive(listener.clone()),
|
listener: EventListenerType::Additive(listener.clone()),
|
||||||
once: false,
|
once: false,
|
||||||
passive: None,
|
passive: None,
|
||||||
};
|
removed: false,
|
||||||
if let Some(position) = entry.iter().position(|e| *e == old_entry) {
|
}));
|
||||||
entry.remove(position);
|
if let Some(position) = entries.iter().position(|e| *e == old_entry) {
|
||||||
|
entries.remove(position).borrow_mut().removed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[remove-all-listeners.html]
|
|
||||||
[Removing all listeners and then adding a new one should work.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Nested usage of once listeners should work.]
|
|
||||||
expected: FAIL
|
|
|
@ -10,6 +10,3 @@
|
||||||
|
|
||||||
[Custom event listeners are to be removed from Window for an active but not fully active document]
|
[Custom event listeners are to be removed from Window for an active but not fully active document]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Event listeners are to be removed with immediate effect]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue