Don't fire slotchange events when there's already a pending event for the same slot (#35222)

* Don't fire slotchange events if there is already a pending event for the same slot

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Update WPT expectations

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

---------

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
Simon Wülker 2025-01-30 14:57:04 +01:00 committed by GitHub
parent 5e9de2cb61
commit 64b40ea700
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 33 additions and 49 deletions

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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 dom_struct::dom_struct;
use html5ever::{local_name, namespace_url, ns, LocalName, Prefix}; 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::element::{AttributeMutation, Element};
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlelement::HTMLElement;
use crate::dom::mutationobserver::MutationObserver;
use crate::dom::node::{Node, ShadowIncluding}; use crate::dom::node::{Node, ShadowIncluding};
use crate::dom::text::Text; use crate::dom::text::Text;
use crate::dom::virtualmethods::VirtualMethods; use crate::dom::virtualmethods::VirtualMethods;
@ -42,6 +43,11 @@ pub struct HTMLSlotElement {
/// <https://html.spec.whatwg.org/multipage/#manually-assigned-nodes> /// <https://html.spec.whatwg.org/multipage/#manually-assigned-nodes>
manually_assigned_nodes: RefCell<Vec<Slottable>>, manually_assigned_nodes: RefCell<Vec<Slottable>>,
/// Whether there is a queued signal change for this element
///
/// Necessary to avoid triggering too many slotchange events
is_in_agents_signal_slots: Cell<bool>,
} }
impl HTMLSlotElementMethods<crate::DomTypeHolder> for HTMLSlotElement { impl HTMLSlotElementMethods<crate::DomTypeHolder> for HTMLSlotElement {
@ -165,6 +171,7 @@ impl HTMLSlotElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document), htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
assigned_nodes: Default::default(), assigned_nodes: Default::default(),
manually_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. // then run signal a slot change for slot.
let slots_are_identical = self.assigned_nodes.borrow().iter().eq(slottables.iter()); let slots_are_identical = self.assigned_nodes.borrow().iter().eq(slottables.iter());
if !slots_are_identical { if !slots_are_identical {
ScriptThread::signal_a_slot_change(self); self.signal_a_slot_change();
} }
// Step 3. Set slots assigned nodes to slottables. // Step 3. Set slots assigned nodes to slottables.
@ -330,6 +337,25 @@ impl HTMLSlotElement {
slottable.set_assigned_slot(DomRoot::from_ref(self)); slottable.set_assigned_slot(DomRoot::from_ref(self));
} }
} }
/// <https://dom.spec.whatwg.org/#signal-a-slot-change>
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 slots relevant agents 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 { impl Slottable {

View file

@ -2171,7 +2171,7 @@ impl Node {
if parent.is_in_a_shadow_tree() { if parent.is_in_a_shadow_tree() {
if let Some(slot_element) = parent.downcast::<HTMLSlotElement>() { if let Some(slot_element) = parent.downcast::<HTMLSlotElement>() {
if !slot_element.has_assigned_nodes() { 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 parent.is_in_a_shadow_tree() {
if let Some(slot_element) = parent.downcast::<HTMLSlotElement>() { if let Some(slot_element) = parent.downcast::<HTMLSlotElement>() {
if !slot_element.has_assigned_nodes() { if !slot_element.has_assigned_nodes() {
ScriptThread::signal_a_slot_change(slot_element); slot_element.signal_a_slot_change();
} }
} }
} }

View file

@ -529,6 +529,9 @@ impl ScriptThread {
.signal_slots .signal_slots
.take() .take()
.into_iter() .into_iter()
.inspect(|slot| {
slot.remove_from_signal_slots();
})
.map(|slot| slot.as_rooted()) .map(|slot| slot.as_rooted())
.collect() .collect()
}) })
@ -3746,15 +3749,6 @@ impl ScriptThread {
) )
} }
} }
/// <https://dom.spec.whatwg.org/#signal-a-slot-change>
pub(crate) fn signal_a_slot_change(slot: &HTMLSlotElement) {
// Step 1. Append slot to slots relevant agents signal slots.
ScriptThread::add_signal_slot(slot);
// Step 2. Queue a mutation observer microtask.
MutationObserver::queue_mutation_observer_microtask();
}
} }
impl Drop for ScriptThread { impl Drop for ScriptThread {

View file

@ -1,40 +1,4 @@
[slotchange-event.html] [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] [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 expected: FAIL