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