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