diff --git a/components/script/dom/htmlslotelement.rs b/components/script/dom/htmlslotelement.rs
index 2e28aeec9c8..3bb7ad21cb8 100644
--- a/components/script/dom/htmlslotelement.rs
+++ b/components/script/dom/htmlslotelement.rs
@@ -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/. */
-use std::cell::RefCell;
+use std::cell::{Cell, RefCell};
use dom_struct::dom_struct;
use html5ever::{local_name, namespace_url, ns, LocalName, Prefix};
@@ -26,6 +26,7 @@ use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlelement::HTMLElement;
+use crate::dom::mutationobserver::MutationObserver;
use crate::dom::node::{Node, ShadowIncluding};
use crate::dom::text::Text;
use crate::dom::virtualmethods::VirtualMethods;
@@ -42,6 +43,11 @@ pub struct HTMLSlotElement {
///
manually_assigned_nodes: RefCell>,
+
+ /// Whether there is a queued signal change for this element
+ ///
+ /// Necessary to avoid triggering too many slotchange events
+ is_in_agents_signal_slots: Cell,
}
impl HTMLSlotElementMethods for HTMLSlotElement {
@@ -165,6 +171,7 @@ impl HTMLSlotElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
assigned_nodes: Default::default(),
manually_assigned_nodes: Default::default(),
+ is_in_agents_signal_slots: Default::default(),
}
}
@@ -319,7 +326,7 @@ impl HTMLSlotElement {
// then run signal a slot change for slot.
let slots_are_identical = self.assigned_nodes.borrow().iter().eq(slottables.iter());
if !slots_are_identical {
- ScriptThread::signal_a_slot_change(self);
+ self.signal_a_slot_change();
}
// Step 3. Set slot’s assigned nodes to slottables.
@@ -330,6 +337,25 @@ impl HTMLSlotElement {
slottable.set_assigned_slot(DomRoot::from_ref(self));
}
}
+
+ ///
+ pub(crate) fn signal_a_slot_change(&self) {
+ if self.is_in_agents_signal_slots.get() {
+ return;
+ }
+ self.is_in_agents_signal_slots.set(true);
+
+ // Step 1. Append slot to slot’s relevant agent’s signal slots.
+ ScriptThread::add_signal_slot(self);
+
+ // Step 2. Queue a mutation observer microtask.
+ MutationObserver::queue_mutation_observer_microtask();
+ }
+
+ pub(crate) fn remove_from_signal_slots(&self) {
+ debug_assert!(self.is_in_agents_signal_slots.get());
+ self.is_in_agents_signal_slots.set(false);
+ }
}
impl Slottable {
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index e6db3eaa9d0..8a309080f0a 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -2171,7 +2171,7 @@ impl Node {
if parent.is_in_a_shadow_tree() {
if let Some(slot_element) = parent.downcast::() {
if !slot_element.has_assigned_nodes() {
- ScriptThread::signal_a_slot_change(slot_element);
+ slot_element.signal_a_slot_change();
}
}
}
@@ -2373,7 +2373,7 @@ impl Node {
if parent.is_in_a_shadow_tree() {
if let Some(slot_element) = parent.downcast::() {
if !slot_element.has_assigned_nodes() {
- ScriptThread::signal_a_slot_change(slot_element);
+ slot_element.signal_a_slot_change();
}
}
}
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index e0c839588fb..ec2c36d1d25 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -529,6 +529,9 @@ impl ScriptThread {
.signal_slots
.take()
.into_iter()
+ .inspect(|slot| {
+ slot.remove_from_signal_slots();
+ })
.map(|slot| slot.as_rooted())
.collect()
})
@@ -3746,15 +3749,6 @@ impl ScriptThread {
)
}
}
-
- ///
- pub(crate) fn signal_a_slot_change(slot: &HTMLSlotElement) {
- // Step 1. Append slot to slot’s relevant agent’s signal slots.
- ScriptThread::add_signal_slot(slot);
-
- // Step 2. Queue a mutation observer microtask.
- MutationObserver::queue_mutation_observer_microtask();
- }
}
impl Drop for ScriptThread {
diff --git a/tests/wpt/meta/shadow-dom/slotchange-event.html.ini b/tests/wpt/meta/shadow-dom/slotchange-event.html.ini
index b3e09651a5e..a9c5ca9ff8a 100644
--- a/tests/wpt/meta/shadow-dom/slotchange-event.html.ini
+++ b/tests/wpt/meta/shadow-dom/slotchange-event.html.ini
@@ -1,40 +1,4 @@
[slotchange-event.html]
- [slotchange event must fire on a default slot element inside an open shadow root in a document]
- expected: FAIL
-
- [slotchange event must fire on a default slot element inside a closed shadow root in a document]
- expected: FAIL
-
- [slotchange event must fire on a default slot element inside an open shadow root not in a document]
- expected: FAIL
-
- [slotchange event must fire on a default slot element inside a closed shadow root not in a document]
- expected: FAIL
-
- [slotchange event must fire on a named slot element insidean open shadow root in a document]
- expected: FAIL
-
- [slotchange event must fire on a named slot element insidea closed shadow root in a document]
- expected: FAIL
-
- [slotchange event must fire on a named slot element insidean open shadow root not in a document]
- expected: FAIL
-
- [slotchange event must fire on a named slot element insidea closed shadow root not in a document]
- expected: FAIL
-
- [slotchange event must fire on a slot element inside an open shadow root in a document even if the slot was removed immediately after the assigned nodes were mutated]
- expected: FAIL
-
- [slotchange event must fire on a slot element inside a closed shadow root in a document even if the slot was removed immediately after the assigned nodes were mutated]
- expected: FAIL
-
- [slotchange event must fire on a slot element inside an open shadow root not in a document even if the slot was removed immediately after the assigned nodes were mutated]
- expected: FAIL
-
- [slotchange event must fire on a slot element inside a closed shadow root not in a document even if the slot was removed immediately after the assigned nodes were mutated]
- expected: FAIL
-
[slotchange event must fire on a slot element inside an open shadow root in a document when innerHTML modifies the children of the shadow host]
expected: FAIL