script/
script_mutation_observers.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::rc::Rc;
7
8use script_bindings::callback::ExceptionHandling;
9use script_bindings::inheritance::Castable;
10use script_bindings::root::{Dom, DomRoot};
11use script_bindings::script_runtime::CanGc;
12
13use crate::dom::bindings::cell::DomRefCell;
14use crate::dom::types::{EventTarget, HTMLSlotElement, MutationObserver, MutationRecord};
15use crate::microtask::{Microtask, MicrotaskQueue};
16
17/// A helper struct for mutation observers used in `ScriptThread`
18/// Since the Rc is always stored in ScriptThread, it's always reachable by the GC.
19#[derive(JSTraceable, Default)]
20#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
21#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
22pub(crate) struct ScriptMutationObservers {
23    /// Microtask Queue for adding support for mutation observer microtasks
24    mutation_observer_microtask_queued: Cell<bool>,
25
26    /// The unit of related similar-origin browsing contexts' list of MutationObserver objects
27    mutation_observers: DomRefCell<Vec<Dom<MutationObserver>>>,
28
29    /// <https://dom.spec.whatwg.org/#signal-slot-list>
30    signal_slots: DomRefCell<Vec<Dom<HTMLSlotElement>>>,
31}
32
33impl ScriptMutationObservers {
34    pub(crate) fn add_mutation_observer(&self, observer: &MutationObserver) {
35        self.mutation_observers
36            .borrow_mut()
37            .push(Dom::from_ref(observer));
38    }
39
40    /// <https://dom.spec.whatwg.org/#notify-mutation-observers>
41    pub(crate) fn notify_mutation_observers(&self, can_gc: CanGc) {
42        // Step 1. Set the surrounding agent’s mutation observer microtask queued to false.
43        self.mutation_observer_microtask_queued.set(false);
44
45        // Step 2. Let notifySet be a clone of the surrounding agent’s pending mutation observers.
46        // TODO Step 3. Empty the surrounding agent’s pending mutation observers.
47        let notify_list = self.mutation_observers.borrow();
48
49        // Step 4. Let signalSet be a clone of the surrounding agent’s signal slots.
50        // Step 5. Empty the surrounding agent’s signal slots.
51        let signal_set: Vec<DomRoot<HTMLSlotElement>> = self.take_signal_slots();
52
53        // Step 6. For each mo of notifySet:
54        for mo in notify_list.iter() {
55            let record_queue = mo.record_queue();
56
57            // Step 6.1 Let records be a clone of mo’s record queue.
58            let queue: Vec<DomRoot<MutationRecord>> = record_queue.borrow().clone();
59
60            // Step 6.2 Empty mo’s record queue.
61            record_queue.borrow_mut().clear();
62
63            // TODO Step 6.3 For each node of mo’s node list, remove all transient registered observers
64            // whose observer is mo from node’s registered observer list.
65
66            // Step 6.4 If records is not empty, then invoke mo’s callback with « records,
67            // mo » and "report", and with callback this value mo.
68            if !queue.is_empty() {
69                let _ = mo
70                    .callback()
71                    .Call_(&**mo, queue, mo, ExceptionHandling::Report, can_gc);
72            }
73        }
74
75        // Step 6. For each slot of signalSet, fire an event named slotchange,
76        // with its bubbles attribute set to true, at slot.
77        for slot in signal_set {
78            slot.upcast::<EventTarget>()
79                .fire_event(atom!("slotchange"), can_gc);
80        }
81    }
82
83    /// <https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask>
84    pub(crate) fn queue_mutation_observer_microtask(&self, microtask_queue: Rc<MicrotaskQueue>) {
85        // Step 1. If the surrounding agent’s mutation observer microtask queued is true, then return.
86        if self.mutation_observer_microtask_queued.get() {
87            return;
88        }
89
90        // Step 2. Set the surrounding agent’s mutation observer microtask queued to true.
91        self.mutation_observer_microtask_queued.set(true);
92
93        // Step 3. Queue a microtask to notify mutation observers.
94        crate::script_thread::with_script_thread(|script_thread| {
95            microtask_queue.enqueue(Microtask::NotifyMutationObservers, script_thread.get_cx());
96        });
97    }
98
99    pub(crate) fn add_signal_slot(&self, observer: &HTMLSlotElement) {
100        self.signal_slots.borrow_mut().push(Dom::from_ref(observer));
101    }
102
103    pub(crate) fn take_signal_slots(&self) -> Vec<DomRoot<HTMLSlotElement>> {
104        self.signal_slots
105            .take()
106            .into_iter()
107            .inspect(|slot| {
108                slot.remove_from_signal_slots();
109            })
110            .map(|slot| slot.as_rooted())
111            .collect()
112    }
113}