Skip to main content

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