diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index 203b5449bd4..61b9b14877e 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -525,7 +525,10 @@ fn inner_invoke( // Step 2.2. found = true; - // TODO: step 2.5. + // Step 2.5. + if let CompiledEventListener::Listener(event_listener) = listener { + object.remove_listener_if_once(&event.type_(), &event_listener); + } // Step 2.6. let marker = TimelineMarker::start("DOMEvent".to_owned()); diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs index 148bdcadb13..69e536f73f9 100644 --- a/components/script/dom/eventtarget.rs +++ b/components/script/dom/eventtarget.rs @@ -244,11 +244,18 @@ impl CompiledEventListener { } } -#[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf, PartialEq)] +#[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf)] /// A listener in a collection of event listeners. struct EventListenerEntry { phase: ListenerPhase, listener: EventListenerType, + once: bool, +} + +impl std::cmp::PartialEq for EventListenerEntry { + fn eq(&self, other: &Self) -> bool { + self.phase == other.phase && self.listener == other.listener + } } #[derive(JSTraceable, MallocSizeOf)] @@ -401,12 +408,22 @@ impl EventTarget { entries.push(EventListenerEntry { phase: ListenerPhase::Bubbling, listener: EventListenerType::Inline(listener), + once: false, }); } }, } } + pub fn remove_listener_if_once(&self, ty: &Atom, listener: &Rc) { + let mut handlers = self.handlers.borrow_mut(); + + let listener = EventListenerType::Additive(listener.clone()); + for entries in handlers.get_mut(ty) { + entries.drain_filter(|e| e.listener == listener && e.once); + } + } + fn get_inline_event_listener(&self, ty: &Atom) -> Option { let mut handlers = self.handlers.borrow_mut(); handlers @@ -662,6 +679,7 @@ impl EventTarget { let new_entry = EventListenerEntry { phase: phase, listener: EventListenerType::Additive(listener), + once: options.once, }; if !entry.contains(&new_entry) { entry.push(new_entry); @@ -690,6 +708,7 @@ impl EventTarget { let old_entry = EventListenerEntry { phase: phase, listener: EventListenerType::Additive(listener.clone()), + once: false, }; if let Some(position) = entry.iter().position(|e| *e == old_entry) { entry.remove(position); @@ -744,6 +763,7 @@ impl From for AddEventListenerOptions { AddEventListenerOptionsOrBoolean::AddEventListenerOptions(options) => options, AddEventListenerOptionsOrBoolean::Boolean(capture) => Self { parent: EventListenerOptions { capture }, + once: false, }, } } diff --git a/components/script/dom/mediaquerylist.rs b/components/script/dom/mediaquerylist.rs index 1730845a4df..45797e0090f 100644 --- a/components/script/dom/mediaquerylist.rs +++ b/components/script/dom/mediaquerylist.rs @@ -95,6 +95,7 @@ impl MediaQueryListMethods for MediaQueryList { listener, AddEventListenerOptions { parent: EventListenerOptions { capture: false }, + once: false, }, ); } diff --git a/components/script/dom/webidls/EventTarget.webidl b/components/script/dom/webidls/EventTarget.webidl index 5921a8ea542..b0a6e77035e 100644 --- a/components/script/dom/webidls/EventTarget.webidl +++ b/components/script/dom/webidls/EventTarget.webidl @@ -29,5 +29,5 @@ dictionary EventListenerOptions { dictionary AddEventListenerOptions : EventListenerOptions { // boolean passive = false; - // boolean once = false; + boolean once = false; }; diff --git a/tests/wpt/metadata/dom/events/AddEventListenerOptions-once.html.ini b/tests/wpt/metadata/dom/events/AddEventListenerOptions-once.html.ini deleted file mode 100644 index ecd6852e1c5..00000000000 --- a/tests/wpt/metadata/dom/events/AddEventListenerOptions-once.html.ini +++ /dev/null @@ -1,12 +0,0 @@ -[AddEventListenerOptions-once.html] - type: testharness - bug: https://github.com/servo/servo/issues/13242 - [Once listener should be invoked only once] - expected: FAIL - - [Once listener should be invoked only once even if the event is nested] - expected: FAIL - - [Once listener should be added / removed like normal listeners] - expected: FAIL -