diff options
author | Simon Wülker <simon.wuelker@arcor.de> | 2025-01-27 15:13:22 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-27 14:13:22 +0000 |
commit | 859cc6ab9b9da0bf92ff623f087ecd728cf120c2 (patch) | |
tree | 58ae026f06e7f648aba6bf13eccb10da29d44069 /components/script | |
parent | cd93841ba188ac89fc18ef597c07d0f135615a7d (diff) | |
download | servo-859cc6ab9b9da0bf92ff623f087ecd728cf120c2.tar.gz servo-859cc6ab9b9da0bf92ff623f087ecd728cf120c2.zip |
Fire slot change events when the slot content changes (#35137)
* Add the onslotchange attribute to ShadowRoot
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Add spec comments to MutationObserver::queue_mutation_observer_microtask
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Add DomRefCell::take
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Add spec comments to notify_mutation_observers
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Fire slotchange events when a slot changes
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* ./mach fmt
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Fix check for when to dispatch slot events
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Potentially fire slot change events in Node::remove
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Update WPT expectations
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* ./mach fmt
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Bump stylo
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Move "signal a slot change" into ScriptThread impl
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
---------
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/bindings/cell.rs | 11 | ||||
-rw-r--r-- | components/script/dom/htmlslotelement.rs | 11 | ||||
-rw-r--r-- | components/script/dom/mutationobserver.rs | 44 | ||||
-rw-r--r-- | components/script/dom/node.rs | 42 | ||||
-rw-r--r-- | components/script/dom/shadowroot.rs | 3 | ||||
-rw-r--r-- | components/script/microtask.rs | 2 | ||||
-rw-r--r-- | components/script/script_thread.rs | 34 |
7 files changed, 135 insertions, 12 deletions
diff --git a/components/script/dom/bindings/cell.rs b/components/script/dom/bindings/cell.rs index df0ca84b060..f0f630672c1 100644 --- a/components/script/dom/bindings/cell.rs +++ b/components/script/dom/bindings/cell.rs @@ -151,3 +151,14 @@ impl<T> DomRefCell<T> { self.value.try_borrow_mut() } } + +impl<T: Default> DomRefCell<T> { + /// Takes the wrapped value, leaving `Default::default()` in its place. + /// + /// # Panics + /// + /// Panics if the value is currently borrowed. + pub(crate) fn take(&self) -> T { + self.value.take() + } +} diff --git a/components/script/dom/htmlslotelement.rs b/components/script/dom/htmlslotelement.rs index b5fcdb91c6d..f60d5f3b5b1 100644 --- a/components/script/dom/htmlslotelement.rs +++ b/components/script/dom/htmlslotelement.rs @@ -32,6 +32,7 @@ use crate::dom::node::{Node, ShadowIncluding}; use crate::dom::text::Text; use crate::dom::virtualmethods::VirtualMethods; use crate::script_runtime::CanGc; +use crate::ScriptThread; /// <https://html.spec.whatwg.org/multipage/#the-slot-element> #[dom_struct] @@ -185,6 +186,10 @@ impl HTMLSlotElement { ) } + pub(crate) fn has_assigned_nodes(&self) -> bool { + !self.assigned_nodes.borrow().is_empty() + } + /// <https://dom.spec.whatwg.org/#find-flattened-slotables> fn find_flattened_slottables(&self, result: &mut RootedVec<Slottable>) { // Step 1. Let result be an empty list. @@ -312,8 +317,12 @@ impl HTMLSlotElement { rooted_vec!(let mut slottables); self.find_slottables(&mut slottables); - // Step 2. TODO If slottables and slot’s assigned nodes are not identical, + // Step 2. If slottables and slot’s assigned nodes are not identical, // 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); + } // Step 3. Set slot’s assigned nodes to slottables. *self.assigned_nodes.borrow_mut() = slottables.iter().cloned().collect(); diff --git a/components/script/dom/mutationobserver.rs b/components/script/dom/mutationobserver.rs index e2c86181097..60628764c52 100644 --- a/components/script/dom/mutationobserver.rs +++ b/components/script/dom/mutationobserver.rs @@ -15,9 +15,11 @@ use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::{ MutationCallback, MutationObserverInit, }; use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; +use crate::dom::eventtarget::EventTarget; use crate::dom::mutationrecord::MutationRecord; use crate::dom::node::{Node, ShadowIncluding}; use crate::dom::window::Window; @@ -90,35 +92,57 @@ impl MutationObserver { /// <https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask> pub(crate) fn queue_mutation_observer_microtask() { - // Step 1 + // Step 1. If the surrounding agent’s mutation observer microtask queued is true, then return. if ScriptThread::is_mutation_observer_microtask_queued() { return; } - // Step 2 + + // Step 2. Set the surrounding agent’s mutation observer microtask queued to true. ScriptThread::set_mutation_observer_microtask_queued(true); - // Step 3 + + // Step 3. Queue a microtask to notify mutation observers. ScriptThread::enqueue_microtask(Microtask::NotifyMutationObservers); } /// <https://dom.spec.whatwg.org/#notify-mutation-observers> - pub(crate) fn notify_mutation_observers() { - // Step 1 + pub(crate) fn notify_mutation_observers(can_gc: CanGc) { + // Step 1. Set the surrounding agent’s mutation observer microtask queued to false. ScriptThread::set_mutation_observer_microtask_queued(false); - // Step 2 + + // Step 2. Let notifySet be a clone of the surrounding agent’s pending mutation observers. + // TODO Step 3. Empty the surrounding agent’s pending mutation observers. let notify_list = ScriptThread::get_mutation_observers(); - // TODO: steps 3-4 (slots) - // Step 5 + + // Step 4. Let signalSet be a clone of the surrounding agent’s signal slots. + // Step 5. Empty the surrounding agent’s signal slots. + let signal_set = ScriptThread::take_signal_slots(); + + // Step 6. For each mo of notifySet: for mo in ¬ify_list { + // Step 6.1 Let records be a clone of mo’s record queue. let queue: Vec<DomRoot<MutationRecord>> = mo.record_queue.borrow().clone(); + + // Step 6.2 Empty mo’s record queue. mo.record_queue.borrow_mut().clear(); - // TODO: Step 5.3 Remove all transient registered observers whose observer is mo. + + // TODO Step 6.3 For each node of mo’s node list, remove all transient registered observers + // whose observer is mo from node’s registered observer list. + + // Step 6.4 If records is not empty, then invoke mo’s callback with « records, + // mo » and "report", and with callback this value mo. if !queue.is_empty() { let _ = mo .callback .Call_(&**mo, queue, mo, ExceptionHandling::Report); } } - // TODO: Step 6 (slot signals) + + // Step 6. For each slot of signalSet, fire an event named slotchange, + // with its bubbles attribute set to true, at slot. + for slot in signal_set { + slot.upcast::<EventTarget>() + .fire_event(atom!("slotchange"), can_gc); + } } /// <https://dom.spec.whatwg.org/#queueing-a-mutation-record> diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index e37e8df1c55..30b15daab05 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -2323,6 +2323,48 @@ impl Node { let old_next_sibling = node.GetNextSibling(); // Steps 9-10 are handled in unbind_from_tree. parent.remove_child(node, cached_index); + + // Step 12. If node is assigned, then run assign slottables for node’s assigned slot. + let assigned_slot = node + .downcast::<Element>() + .and_then(|e| e.assigned_slot()) + .or_else(|| { + node.downcast::<Text>().and_then(|text| { + text.slottable_data() + .borrow() + .assigned_slot + .as_ref() + .map(Dom::as_rooted) + }) + }); + if let Some(slot) = assigned_slot { + slot.assign_slottables(); + } + + // Step 13. If parent’s root is a shadow root, and parent is a slot whose assigned nodes is the empty list, + // then run signal a slot change for parent. + if parent.is_in_a_shadow_tree() { + if let Some(slot_element) = parent.downcast::<HTMLSlotElement>() { + if !slot_element.has_assigned_nodes() { + ScriptThread::signal_a_slot_change(slot_element); + } + } + } + + // Step 14. If node has an inclusive descendant that is a slot: + let has_slot_descendant = node + .traverse_preorder(ShadowIncluding::No) + .any(|elem| elem.is::<HTMLSlotElement>()); + if has_slot_descendant { + // Step 14.1 Run assign slottables for a tree with parent’s root. + parent + .GetRootNode(&GetRootNodeOptions::empty()) + .assign_slottables_for_a_tree(); + + // Step 14.2 Run assign slottables for a tree with node. + node.assign_slottables_for_a_tree(); + } + // Step 11. transient registered observers // Step 12. if let SuppressObserver::Unsuppressed = suppress_observers { diff --git a/components/script/dom/shadowroot.rs b/components/script/dom/shadowroot.rs index 46fcb38cf1b..872a3a461d5 100644 --- a/components/script/dom/shadowroot.rs +++ b/components/script/dom/shadowroot.rs @@ -325,6 +325,9 @@ impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot { fn SlotAssignment(&self) -> SlotAssignmentMode { self.slot_assignment_mode } + + // https://dom.spec.whatwg.org/#dom-shadowroot-onslotchange + event_handler!(onslotchange, GetOnslotchange, SetOnslotchange); } impl VirtualMethods for ShadowRoot { diff --git a/components/script/microtask.rs b/components/script/microtask.rs index b186d7aa337..94a675d78c9 100644 --- a/components/script/microtask.rs +++ b/components/script/microtask.rs @@ -140,7 +140,7 @@ impl MicrotaskQueue { ScriptThread::invoke_backup_element_queue(can_gc); }, Microtask::NotifyMutationObservers => { - MutationObserver::notify_mutation_observers(); + MutationObserver::notify_mutation_observers(can_gc); }, Microtask::ReadableStreamTeeReadRequest(ref task) => { task.microtask_chunk_steps(can_gc) diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 13558b81832..c232fb9fe51 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -128,6 +128,7 @@ use crate::dom::element::Element; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlanchorelement::HTMLAnchorElement; use crate::dom::htmliframeelement::HTMLIFrameElement; +use crate::dom::htmlslotelement::HTMLSlotElement; use crate::dom::mutationobserver::MutationObserver; use crate::dom::node::{Node, NodeTraits, ShadowIncluding}; use crate::dom::performanceentry::PerformanceEntry; @@ -259,6 +260,9 @@ pub struct ScriptThread { /// The unit of related similar-origin browsing contexts' list of MutationObserver objects mutation_observers: DomRefCell<Vec<Dom<MutationObserver>>>, + /// <https://dom.spec.whatwg.org/#signal-slot-list> + signal_slots: DomRefCell<Vec<Dom<HTMLSlotElement>>>, + /// A handle to the WebGL thread #[no_trace] webgl_chan: Option<WebGLPipeline>, @@ -508,6 +512,26 @@ impl ScriptThread { }) } + pub(crate) fn add_signal_slot(observer: &HTMLSlotElement) { + with_script_thread(|script_thread| { + script_thread + .signal_slots + .borrow_mut() + .push(Dom::from_ref(observer)); + }) + } + + pub(crate) fn take_signal_slots() -> Vec<DomRoot<HTMLSlotElement>> { + with_script_thread(|script_thread| { + script_thread + .signal_slots + .take() + .into_iter() + .map(|slot| slot.as_rooted()) + .collect() + }) + } + pub(crate) fn mark_document_with_no_blocked_loads(doc: &Document) { with_script_thread(|script_thread| { script_thread @@ -914,6 +938,7 @@ impl ScriptThread { closed_pipelines: DomRefCell::new(HashSet::new()), mutation_observer_microtask_queued: Default::default(), mutation_observers: Default::default(), + signal_slots: Default::default(), system_font_service, webgl_chan: state.webgl_chan, #[cfg(feature = "webxr")] @@ -3718,6 +3743,15 @@ 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 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 { |