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