Files
servo/components/script/script_mutation_observers.rs
Rodion Borovyk 5b1fe60277 script: Empty pending mutation observers when notifying mutation observers (#39456)
Empty the surrounding agent’s pending mutation observers when notifying
mutation observers according to the spec. Also, the code in the method
MutationObserver::queue_a_mutation_record and the corresponding
specification have diverged over the years. These changes bring the code
into conformity with the specification.

Testing: Added a new crash test
Fixes: #39434 #39531

---------

Signed-off-by: Rodion Borovyk <rodion.borovyk@gmail.com>
2025-09-29 14:15:07 +00:00

122 lines
4.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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::Cell;
use std::rc::Rc;
use script_bindings::callback::ExceptionHandling;
use script_bindings::inheritance::Castable;
use script_bindings::root::{Dom, DomRoot};
use script_bindings::script_runtime::CanGc;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::types::{EventTarget, HTMLSlotElement, MutationObserver, MutationRecord};
use crate::microtask::{Microtask, MicrotaskQueue};
/// A helper struct for mutation observers used in `ScriptThread`
/// Since the Rc is always stored in ScriptThread, it's always reachable by the GC.
#[derive(JSTraceable, Default)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct ScriptMutationObservers {
/// Microtask Queue for adding support for mutation observer microtasks
mutation_observer_microtask_queued: Cell<bool>,
/// 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>>>,
}
impl ScriptMutationObservers {
pub(crate) fn add_mutation_observer(&self, observer: &MutationObserver) {
self.mutation_observers
.borrow_mut()
.push(Dom::from_ref(observer));
}
/// <https://dom.spec.whatwg.org/#notify-mutation-observers>
pub(crate) fn notify_mutation_observers(&self, can_gc: CanGc) {
// Step 1. Set the surrounding agents mutation observer microtask queued to false.
self.mutation_observer_microtask_queued.set(false);
// Step 2. Let notifySet be a clone of the surrounding agents pending mutation observers.
// Step 3. Empty the surrounding agents pending mutation observers.
let notify_list = self.take_mutation_observers();
// Step 4. Let signalSet be a clone of the surrounding agents signal slots.
// Step 5. Empty the surrounding agents signal slots.
let signal_set: Vec<DomRoot<HTMLSlotElement>> = self.take_signal_slots();
// Step 6. For each mo of notifySet:
for mo in notify_list.iter() {
let record_queue = mo.record_queue();
// Step 6.1 Let records be a clone of mos record queue.
let queue: Vec<DomRoot<MutationRecord>> = record_queue.borrow().clone();
// Step 6.2 Empty mos record queue.
record_queue.borrow_mut().clear();
// TODO Step 6.3 For each node of mos node list, remove all transient registered observers
// whose observer is mo from nodes registered observer list.
// Step 6.4 If records is not empty, then invoke mos 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, can_gc);
}
}
// 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/#queue-a-mutation-observer-compound-microtask>
pub(crate) fn queue_mutation_observer_microtask(&self, microtask_queue: Rc<MicrotaskQueue>) {
// Step 1. If the surrounding agents mutation observer microtask queued is true, then return.
if self.mutation_observer_microtask_queued.get() {
return;
}
// Step 2. Set the surrounding agents mutation observer microtask queued to true.
self.mutation_observer_microtask_queued.set(true);
// Step 3. Queue a microtask to notify mutation observers.
crate::script_thread::with_script_thread(|script_thread| {
microtask_queue.enqueue(Microtask::NotifyMutationObservers, script_thread.get_cx());
});
}
pub(crate) fn add_signal_slot(&self, observer: &HTMLSlotElement) {
self.signal_slots.borrow_mut().push(Dom::from_ref(observer));
}
pub(crate) fn take_signal_slots(&self) -> Vec<DomRoot<HTMLSlotElement>> {
self.signal_slots
.take()
.into_iter()
.inspect(|slot| {
slot.remove_from_signal_slots();
})
.map(|slot| slot.as_rooted())
.collect()
}
pub(crate) fn take_mutation_observers(&self) -> Vec<DomRoot<MutationObserver>> {
self.mutation_observers
.take()
.iter()
.map(|mo| mo.as_rooted())
.collect()
}
}