Skip to main content

script/dom/mutationobserver/
mutationobserver.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::LazyCell;
6use std::collections::HashMap;
7use std::rc::Rc;
8
9use dom_struct::dom_struct;
10use html5ever::{LocalName, Namespace, ns};
11use js::rust::HandleObject;
12use script_bindings::cell::DomRefCell;
13use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
14
15use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserver_Binding::MutationObserverMethods;
16use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::{
17    MutationCallback, MutationObserverInit,
18};
19use crate::dom::bindings::error::{Error, Fallible};
20use crate::dom::bindings::reflector::DomGlobal;
21use crate::dom::bindings::root::{Dom, DomRoot};
22use crate::dom::bindings::str::DOMString;
23use crate::dom::iterators::ShadowIncluding;
24use crate::dom::mutationrecord::MutationRecord;
25use crate::dom::node::Node;
26use crate::dom::window::Window;
27use crate::script_runtime::CanGc;
28use crate::script_thread::ScriptThread;
29
30#[dom_struct]
31pub(crate) struct MutationObserver {
32    reflector_: Reflector,
33    #[conditional_malloc_size_of]
34    callback: Rc<MutationCallback>,
35    record_queue: DomRefCell<Vec<Dom<MutationRecord>>>,
36    node_list: DomRefCell<Vec<Dom<Node>>>,
37}
38
39pub(crate) enum Mutation<'a> {
40    Attribute {
41        name: LocalName,
42        namespace: Namespace,
43        old_value: Option<DOMString>,
44    },
45    CharacterData {
46        old_value: String,
47    },
48    ChildList {
49        added: Option<&'a [&'a Node]>,
50        removed: Option<&'a [&'a Node]>,
51        prev: Option<&'a Node>,
52        next: Option<&'a Node>,
53    },
54}
55
56#[derive(JSTraceable, MallocSizeOf)]
57#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
58pub(crate) struct RegisteredObserver {
59    pub(crate) observer: Dom<MutationObserver>,
60    options: ObserverOptions,
61}
62
63#[derive(JSTraceable, MallocSizeOf)]
64pub(crate) struct ObserverOptions {
65    attribute_old_value: bool,
66    attributes: bool,
67    character_data: bool,
68    character_data_old_value: bool,
69    child_list: bool,
70    subtree: bool,
71    attribute_filter: Vec<DOMString>,
72}
73
74impl MutationObserver {
75    fn new_with_proto(
76        global: &Window,
77        proto: Option<HandleObject>,
78        callback: Rc<MutationCallback>,
79        can_gc: CanGc,
80    ) -> DomRoot<MutationObserver> {
81        let boxed_observer = Box::new(MutationObserver::new_inherited(callback));
82        reflect_dom_object_with_proto(boxed_observer, global, proto, can_gc)
83    }
84
85    fn new_inherited(callback: Rc<MutationCallback>) -> MutationObserver {
86        MutationObserver {
87            reflector_: Reflector::new(),
88            callback,
89            record_queue: DomRefCell::new(vec![]),
90            node_list: DomRefCell::new(vec![]),
91        }
92    }
93
94    pub(crate) fn record_queue(&self) -> &DomRefCell<Vec<Dom<MutationRecord>>> {
95        &self.record_queue
96    }
97
98    pub(crate) fn callback(&self) -> &Rc<MutationCallback> {
99        &self.callback
100    }
101
102    /// <https://dom.spec.whatwg.org/#queueing-a-mutation-record>
103    pub(crate) fn queue_a_mutation_record<'a, F>(
104        target: &Node,
105        attr_type: LazyCell<Mutation<'a>, F>,
106    ) where
107        F: FnOnce() -> Mutation<'a>,
108    {
109        if !target.global().as_window().get_exists_mut_observer() {
110            return;
111        }
112        // Step 1 Let interestedObservers be an empty map.
113        let mut interested_observers: HashMap<DomRoot<MutationObserver>, Option<DOMString>> =
114            HashMap::new();
115
116        // Step 2 Let nodes be the inclusive ancestors of target.
117        // Step 3 For each node in nodes ...
118        for node in target.inclusive_ancestors(ShadowIncluding::No) {
119            let registered = node.registered_mutation_observers();
120            if registered.is_none() {
121                continue;
122            }
123
124            // Step 3 ... and then for each registered of node’s registered observer list:
125            for registered in &*registered.unwrap() {
126                // 3.2 "1": node is not target and options["subtree"] is false
127                if &*node != target && !registered.options.subtree {
128                    continue;
129                }
130
131                match *attr_type {
132                    // 3.2 "2", "3"
133                    Mutation::Attribute {
134                        ref name,
135                        ref namespace,
136                        ref old_value,
137                    } => {
138                        // 3.1.2 "2": type is "attributes" and options["attributes"] either does not exist or is false
139                        if !registered.options.attributes {
140                            continue;
141                        }
142                        // 3.1.2 "3": type is "attributes", options["attributeFilter"] exists,
143                        // and options["attributeFilter"] does not contain name or namespace is non-null
144                        if !registered.options.attribute_filter.is_empty() {
145                            if *namespace != ns!() {
146                                continue;
147                            }
148                            if !registered
149                                .options
150                                .attribute_filter
151                                .iter()
152                                .any(|s| *s == **name)
153                            {
154                                continue;
155                            }
156                        }
157                        // 3.2.1 Let mo be registered’s observer.
158                        let mo = registered.observer.as_rooted();
159                        // 3.2.2 If interestedObservers[mo] does not exist, then set interestedObservers[mo] to null.
160                        if registered.options.attribute_old_value {
161                            // 3.2.3 ... type is "attributes" and options["attributeOldValue"] is true ...
162                            interested_observers.insert(mo, old_value.clone());
163                        } else {
164                            // 3.2.2 If interestedObservers[mo] does not exist, then set interestedObservers[mo] to null.
165                            interested_observers.entry(mo).or_insert(None);
166                        }
167                    },
168                    // 3.2 "4"
169                    Mutation::CharacterData { ref old_value } => {
170                        // 3.2 "4": type is "characterData" and options["characterData"] either does not exist or is false
171                        if !registered.options.character_data {
172                            continue;
173                        }
174                        // 3.2.1 Let mo be registered’s observer.
175                        let mo = registered.observer.as_rooted();
176                        if registered.options.character_data_old_value {
177                            // 3.2.3 ... type is "characterData" and options["characterDataOldValue"] is true
178                            interested_observers
179                                .insert(mo, Some(DOMString::from(old_value.clone())));
180                        } else {
181                            // 3.2.2 If interestedObservers[mo] does not exist, then set interestedObservers[mo] to null.
182                            interested_observers.entry(mo).or_insert(None);
183                        }
184                    },
185                    // 3.2 "5"
186                    Mutation::ChildList { .. } => {
187                        // 3.2 "5": type is "childList" and options["childList"] is false
188                        if !registered.options.child_list {
189                            continue;
190                        }
191                        // 3.2.1 Let mo be registered’s observer.
192                        let mo = registered.observer.as_rooted();
193                        // 3.2.2 If interestedObservers[mo] does not exist, then set interestedObservers[mo] to null.
194                        interested_observers.entry(mo).or_insert(None);
195                    },
196                }
197            }
198        }
199
200        // Step 4 For each observer → mappedOldValue of interestedObservers:
201        for (observer, mapped_old_value) in interested_observers {
202            // Step 4.1 Let record be a new MutationRecord object ...
203            let record = match *attr_type {
204                Mutation::Attribute {
205                    ref name,
206                    ref namespace,
207                    ..
208                } => {
209                    let namespace = if *namespace != ns!() {
210                        Some(namespace)
211                    } else {
212                        None
213                    };
214                    MutationRecord::attribute_mutated(
215                        target,
216                        name,
217                        namespace,
218                        mapped_old_value,
219                        CanGc::deprecated_note(),
220                    )
221                },
222                Mutation::CharacterData { .. } => MutationRecord::character_data_mutated(
223                    target,
224                    mapped_old_value,
225                    CanGc::deprecated_note(),
226                ),
227                Mutation::ChildList {
228                    ref added,
229                    ref removed,
230                    ref next,
231                    ref prev,
232                } => MutationRecord::child_list_mutated(
233                    target,
234                    *added,
235                    *removed,
236                    *next,
237                    *prev,
238                    CanGc::deprecated_note(),
239                ),
240            };
241            // Step 4.2 Enqueue record to observer’s record queue.
242            observer
243                .record_queue
244                .borrow_mut()
245                .push(Dom::from_ref(&*record));
246            // Step 4.3 Append observer to the surrounding agent’s pending mutation observers.
247            ScriptThread::mutation_observers().add_mutation_observer(&observer);
248        }
249
250        // Step 5 Queue a mutation observer microtask.
251        let mutation_observers = ScriptThread::mutation_observers();
252        mutation_observers.queue_mutation_observer_microtask(ScriptThread::microtask_queue());
253    }
254}
255
256impl MutationObserverMethods<crate::DomTypeHolder> for MutationObserver {
257    /// <https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver>
258    fn Constructor(
259        global: &Window,
260        proto: Option<HandleObject>,
261        can_gc: CanGc,
262        callback: Rc<MutationCallback>,
263    ) -> Fallible<DomRoot<MutationObserver>> {
264        global.set_exists_mut_observer();
265        let observer = MutationObserver::new_with_proto(global, proto, callback, can_gc);
266        ScriptThread::mutation_observers().add_mutation_observer(&observer);
267        Ok(observer)
268    }
269
270    /// <https://dom.spec.whatwg.org/#dom-mutationobserver-observe>
271    fn Observe(&self, target: &Node, options: &MutationObserverInit) -> Fallible<()> {
272        let attribute_filter = options.attributeFilter.clone().unwrap_or_default();
273        let attribute_old_value = options.attributeOldValue.unwrap_or(false);
274        let mut attributes = options.attributes.unwrap_or(false);
275        let mut character_data = options.characterData.unwrap_or(false);
276        let character_data_old_value = options.characterDataOldValue.unwrap_or(false);
277        let child_list = options.childList;
278        let subtree = options.subtree;
279
280        // Step 1
281        if (options.attributeOldValue.is_some() || options.attributeFilter.is_some()) &&
282            options.attributes.is_none()
283        {
284            attributes = true;
285        }
286
287        // Step 2
288        if options.characterDataOldValue.is_some() && options.characterData.is_none() {
289            character_data = true;
290        }
291
292        // Step 3
293        if !child_list && !attributes && !character_data {
294            return Err(Error::Type(
295                c"One of childList, attributes, or characterData must be true".into(),
296            ));
297        }
298
299        // Step 4
300        if attribute_old_value && !attributes {
301            return Err(Error::Type(
302                c"attributeOldValue is true but attributes is false".into(),
303            ));
304        }
305
306        // Step 5
307        if options.attributeFilter.is_some() && !attributes {
308            return Err(Error::Type(
309                c"attributeFilter is present but attributes is false".into(),
310            ));
311        }
312
313        // Step 6
314        if character_data_old_value && !character_data {
315            return Err(Error::Type(
316                c"characterDataOldValue is true but characterData is false".into(),
317            ));
318        }
319
320        // Step 7
321        let add_new_observer = {
322            let mut replaced = false;
323            for registered in &mut *target.registered_mutation_observers_mut() {
324                if !std::ptr::eq(&*registered.observer, self) {
325                    continue;
326                }
327                // TODO: remove matching transient registered observers
328                registered.options.attribute_old_value = attribute_old_value;
329                registered.options.attributes = attributes;
330                registered.options.character_data = character_data;
331                registered.options.character_data_old_value = character_data_old_value;
332                registered.options.child_list = child_list;
333                registered.options.subtree = subtree;
334                registered
335                    .options
336                    .attribute_filter
337                    .clone_from(&attribute_filter);
338                replaced = true;
339            }
340            !replaced
341        };
342
343        // Step 8
344        if add_new_observer {
345            target.add_mutation_observer(RegisteredObserver {
346                observer: Dom::from_ref(self),
347                options: ObserverOptions {
348                    attributes,
349                    attribute_old_value,
350                    character_data,
351                    character_data_old_value,
352                    subtree,
353                    attribute_filter,
354                    child_list,
355                },
356            });
357
358            self.node_list.borrow_mut().push(Dom::from_ref(target));
359        }
360
361        Ok(())
362    }
363
364    /// <https://dom.spec.whatwg.org/#dom-mutationobserver-takerecords>
365    fn TakeRecords(&self) -> Vec<DomRoot<MutationRecord>> {
366        let records: Vec<DomRoot<MutationRecord>> = self
367            .record_queue
368            .borrow()
369            .iter()
370            .map(|record| record.as_rooted())
371            .collect();
372        self.record_queue.borrow_mut().clear();
373        records
374    }
375
376    /// <https://dom.spec.whatwg.org/#dom-mutationobserver-disconnect>
377    fn Disconnect(&self) {
378        // Step 1
379        let nodes = self
380            .node_list
381            .borrow()
382            .iter()
383            .map(|node| node.as_rooted())
384            .collect::<Vec<_>>();
385        self.node_list.borrow_mut().clear();
386
387        for node in nodes {
388            node.remove_mutation_observer(self);
389        }
390
391        // Step 2
392        self.record_queue.borrow_mut().clear();
393    }
394}