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