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