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;
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    #[conditional_malloc_size_of]
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::deprecated_note(),
218                    )
219                },
220                Mutation::CharacterData { .. } => MutationRecord::character_data_mutated(
221                    target,
222                    mapped_old_value,
223                    CanGc::deprecated_note(),
224                ),
225                Mutation::ChildList {
226                    ref added,
227                    ref removed,
228                    ref next,
229                    ref prev,
230                } => MutationRecord::child_list_mutated(
231                    target,
232                    *added,
233                    *removed,
234                    *next,
235                    *prev,
236                    CanGc::deprecated_note(),
237                ),
238            };
239            // Step 4.2 Enqueue record to observer’s record queue.
240            observer
241                .record_queue
242                .borrow_mut()
243                .push(Dom::from_ref(&*record));
244            // Step 4.3 Append observer to the surrounding agent’s pending mutation observers.
245            ScriptThread::mutation_observers().add_mutation_observer(&observer);
246        }
247
248        // Step 5 Queue a mutation observer microtask.
249        let mutation_observers = ScriptThread::mutation_observers();
250        mutation_observers.queue_mutation_observer_microtask(ScriptThread::microtask_queue());
251    }
252}
253
254impl MutationObserverMethods<crate::DomTypeHolder> for MutationObserver {
255    /// <https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver>
256    fn Constructor(
257        global: &Window,
258        proto: Option<HandleObject>,
259        can_gc: CanGc,
260        callback: Rc<MutationCallback>,
261    ) -> Fallible<DomRoot<MutationObserver>> {
262        global.set_exists_mut_observer();
263        let observer = MutationObserver::new_with_proto(global, proto, callback, can_gc);
264        ScriptThread::mutation_observers().add_mutation_observer(&observer);
265        Ok(observer)
266    }
267
268    /// <https://dom.spec.whatwg.org/#dom-mutationobserver-observe>
269    fn Observe(&self, target: &Node, options: &MutationObserverInit) -> Fallible<()> {
270        let attribute_filter = options.attributeFilter.clone().unwrap_or_default();
271        let attribute_old_value = options.attributeOldValue.unwrap_or(false);
272        let mut attributes = options.attributes.unwrap_or(false);
273        let mut character_data = options.characterData.unwrap_or(false);
274        let character_data_old_value = options.characterDataOldValue.unwrap_or(false);
275        let child_list = options.childList;
276        let subtree = options.subtree;
277
278        // Step 1
279        if (options.attributeOldValue.is_some() || options.attributeFilter.is_some()) &&
280            options.attributes.is_none()
281        {
282            attributes = true;
283        }
284
285        // Step 2
286        if options.characterDataOldValue.is_some() && options.characterData.is_none() {
287            character_data = true;
288        }
289
290        // Step 3
291        if !child_list && !attributes && !character_data {
292            return Err(Error::Type(
293                c"One of childList, attributes, or characterData must be true".into(),
294            ));
295        }
296
297        // Step 4
298        if attribute_old_value && !attributes {
299            return Err(Error::Type(
300                c"attributeOldValue is true but attributes is false".into(),
301            ));
302        }
303
304        // Step 5
305        if options.attributeFilter.is_some() && !attributes {
306            return Err(Error::Type(
307                c"attributeFilter is present but attributes is false".into(),
308            ));
309        }
310
311        // Step 6
312        if character_data_old_value && !character_data {
313            return Err(Error::Type(
314                c"characterDataOldValue is true but characterData is false".into(),
315            ));
316        }
317
318        // Step 7
319        let add_new_observer = {
320            let mut replaced = false;
321            for registered in &mut *target.registered_mutation_observers_mut() {
322                if !std::ptr::eq(&*registered.observer, self) {
323                    continue;
324                }
325                // TODO: remove matching transient registered observers
326                registered.options.attribute_old_value = attribute_old_value;
327                registered.options.attributes = attributes;
328                registered.options.character_data = character_data;
329                registered.options.character_data_old_value = character_data_old_value;
330                registered.options.child_list = child_list;
331                registered.options.subtree = subtree;
332                registered
333                    .options
334                    .attribute_filter
335                    .clone_from(&attribute_filter);
336                replaced = true;
337            }
338            !replaced
339        };
340
341        // Step 8
342        if add_new_observer {
343            target.add_mutation_observer(RegisteredObserver {
344                observer: Dom::from_ref(self),
345                options: ObserverOptions {
346                    attributes,
347                    attribute_old_value,
348                    character_data,
349                    character_data_old_value,
350                    subtree,
351                    attribute_filter,
352                    child_list,
353                },
354            });
355
356            self.node_list.borrow_mut().push(Dom::from_ref(target));
357        }
358
359        Ok(())
360    }
361
362    /// <https://dom.spec.whatwg.org/#dom-mutationobserver-takerecords>
363    fn TakeRecords(&self) -> Vec<DomRoot<MutationRecord>> {
364        let records: Vec<DomRoot<MutationRecord>> = self
365            .record_queue
366            .borrow()
367            .iter()
368            .map(|record| record.as_rooted())
369            .collect();
370        self.record_queue.borrow_mut().clear();
371        records
372    }
373
374    /// <https://dom.spec.whatwg.org/#dom-mutationobserver-disconnect>
375    fn Disconnect(&self) {
376        // Step 1
377        let nodes = self
378            .node_list
379            .borrow()
380            .iter()
381            .map(|node| node.as_rooted())
382            .collect::<Vec<_>>();
383        self.node_list.borrow_mut().clear();
384
385        for node in nodes {
386            node.remove_mutation_observer(self);
387        }
388
389        // Step 2
390        self.record_queue.borrow_mut().clear();
391    }
392}