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