/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::LazyCell; use std::rc::Rc; use dom_struct::dom_struct; use html5ever::{LocalName, Namespace, namespace_url, ns}; use js::rust::HandleObject; use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserver_Binding::MutationObserverMethods; use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::{ MutationCallback, MutationObserverInit, }; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::eventtarget::EventTarget; use crate::dom::mutationrecord::MutationRecord; use crate::dom::node::{Node, ShadowIncluding}; use crate::dom::window::Window; use crate::microtask::Microtask; use crate::script_runtime::CanGc; use crate::script_thread::ScriptThread; #[dom_struct] pub(crate) struct MutationObserver { reflector_: Reflector, #[ignore_malloc_size_of = "can't measure Rc values"] callback: Rc, record_queue: DomRefCell>>, node_list: DomRefCell>>, } pub(crate) enum Mutation<'a> { Attribute { name: LocalName, namespace: Namespace, old_value: Option, }, CharacterData { old_value: DOMString, }, ChildList { added: Option<&'a [&'a Node]>, removed: Option<&'a [&'a Node]>, prev: Option<&'a Node>, next: Option<&'a Node>, }, } #[derive(JSTraceable, MallocSizeOf)] pub(crate) struct RegisteredObserver { pub(crate) observer: DomRoot, options: ObserverOptions, } #[derive(JSTraceable, MallocSizeOf)] pub(crate) struct ObserverOptions { attribute_old_value: bool, attributes: bool, character_data: bool, character_data_old_value: bool, child_list: bool, subtree: bool, attribute_filter: Vec, } impl MutationObserver { fn new_with_proto( global: &Window, proto: Option, callback: Rc, can_gc: CanGc, ) -> DomRoot { let boxed_observer = Box::new(MutationObserver::new_inherited(callback)); reflect_dom_object_with_proto(boxed_observer, global, proto, can_gc) } fn new_inherited(callback: Rc) -> MutationObserver { MutationObserver { reflector_: Reflector::new(), callback, record_queue: DomRefCell::new(vec![]), node_list: DomRefCell::new(vec![]), } } /// pub(crate) fn queue_mutation_observer_microtask() { // Step 1. If the surrounding agent’s mutation observer microtask queued is true, then return. if ScriptThread::is_mutation_observer_microtask_queued() { return; } // Step 2. Set the surrounding agent’s mutation observer microtask queued to true. ScriptThread::set_mutation_observer_microtask_queued(true); // Step 3. Queue a microtask to notify mutation observers. ScriptThread::enqueue_microtask(Microtask::NotifyMutationObservers); } /// pub(crate) fn notify_mutation_observers(can_gc: CanGc) { // Step 1. Set the surrounding agent’s mutation observer microtask queued to false. ScriptThread::set_mutation_observer_microtask_queued(false); // Step 2. Let notifySet be a clone of the surrounding agent’s pending mutation observers. // TODO Step 3. Empty the surrounding agent’s pending mutation observers. let notify_list = ScriptThread::get_mutation_observers(); // Step 4. Let signalSet be a clone of the surrounding agent’s signal slots. // Step 5. Empty the surrounding agent’s signal slots. let signal_set = ScriptThread::take_signal_slots(); // Step 6. For each mo of notifySet: for mo in ¬ify_list { // Step 6.1 Let records be a clone of mo’s record queue. let queue: Vec> = mo.record_queue.borrow().clone(); // Step 6.2 Empty mo’s record queue. mo.record_queue.borrow_mut().clear(); // TODO Step 6.3 For each node of mo’s node list, remove all transient registered observers // whose observer is mo from node’s registered observer list. // Step 6.4 If records is not empty, then invoke mo’s callback with « records, // mo » and "report", and with callback this value mo. if !queue.is_empty() { let _ = mo .callback .Call_(&**mo, queue, mo, ExceptionHandling::Report, can_gc); } } // Step 6. For each slot of signalSet, fire an event named slotchange, // with its bubbles attribute set to true, at slot. for slot in signal_set { slot.upcast::() .fire_event(atom!("slotchange"), can_gc); } } /// pub(crate) fn queue_a_mutation_record<'a, F>( target: &Node, attr_type: LazyCell, F>, ) where F: FnOnce() -> Mutation<'a>, { if !target.global().as_window().get_exists_mut_observer() { return; } // Step 1 let mut interested_observers: Vec<(DomRoot, Option)> = vec![]; // Step 2 & 3 for node in target.inclusive_ancestors(ShadowIncluding::No) { let registered = node.registered_mutation_observers(); if registered.is_none() { continue; } for registered in &*registered.unwrap() { if &*node != target && !registered.options.subtree { continue; } match *attr_type { Mutation::Attribute { ref name, ref namespace, ref old_value, } => { // Step 3.1 if !registered.options.attributes { continue; } if !registered.options.attribute_filter.is_empty() { if *namespace != ns!() { continue; } if !registered .options .attribute_filter .iter() .any(|s| **s == **name) { continue; } } // Step 3.1.2 let paired_string = if registered.options.attribute_old_value { old_value.clone() } else { None }; // Step 3.1.1 let idx = interested_observers .iter() .position(|(o, _)| std::ptr::eq(&**o, &*registered.observer)); if let Some(idx) = idx { interested_observers[idx].1 = paired_string; } else { interested_observers .push((DomRoot::from_ref(&*registered.observer), paired_string)); } }, Mutation::CharacterData { ref old_value } => { if !registered.options.character_data { continue; } // Step 3.1.2 let paired_string = if registered.options.character_data_old_value { Some(old_value.clone()) } else { None }; // Step 3.1.1 let idx = interested_observers .iter() .position(|(o, _)| std::ptr::eq(&**o, &*registered.observer)); if let Some(idx) = idx { interested_observers[idx].1 = paired_string; } else { interested_observers .push((DomRoot::from_ref(&*registered.observer), paired_string)); } }, Mutation::ChildList { .. } => { if !registered.options.child_list { continue; } interested_observers.push((DomRoot::from_ref(&*registered.observer), None)); }, } } } // Step 4 for (observer, paired_string) in interested_observers { // Steps 4.1-4.7 let record = match *attr_type { Mutation::Attribute { ref name, ref namespace, .. } => { let namespace = if *namespace != ns!() { Some(namespace) } else { None }; MutationRecord::attribute_mutated( target, name, namespace, paired_string, CanGc::note(), ) }, Mutation::CharacterData { .. } => { MutationRecord::character_data_mutated(target, paired_string, CanGc::note()) }, Mutation::ChildList { ref added, ref removed, ref next, ref prev, } => MutationRecord::child_list_mutated( target, *added, *removed, *next, *prev, CanGc::note(), ), }; // Step 4.8 observer.record_queue.borrow_mut().push(record); } // Step 5 MutationObserver::queue_mutation_observer_microtask(); } } impl MutationObserverMethods for MutationObserver { /// fn Constructor( global: &Window, proto: Option, can_gc: CanGc, callback: Rc, ) -> Fallible> { global.set_exists_mut_observer(); let observer = MutationObserver::new_with_proto(global, proto, callback, can_gc); ScriptThread::add_mutation_observer(&observer); Ok(observer) } /// fn Observe(&self, target: &Node, options: &MutationObserverInit) -> Fallible<()> { let attribute_filter = options.attributeFilter.clone().unwrap_or_default(); let attribute_old_value = options.attributeOldValue.unwrap_or(false); let mut attributes = options.attributes.unwrap_or(false); let mut character_data = options.characterData.unwrap_or(false); let character_data_old_value = options.characterDataOldValue.unwrap_or(false); let child_list = options.childList; let subtree = options.subtree; // Step 1 if (options.attributeOldValue.is_some() || options.attributeFilter.is_some()) && options.attributes.is_none() { attributes = true; } // Step 2 if options.characterDataOldValue.is_some() && options.characterData.is_none() { character_data = true; } // Step 3 if !child_list && !attributes && !character_data { return Err(Error::Type( "One of childList, attributes, or characterData must be true".into(), )); } // Step 4 if attribute_old_value && !attributes { return Err(Error::Type( "attributeOldValue is true but attributes is false".into(), )); } // Step 5 if options.attributeFilter.is_some() && !attributes { return Err(Error::Type( "attributeFilter is present but attributes is false".into(), )); } // Step 6 if character_data_old_value && !character_data { return Err(Error::Type( "characterDataOldValue is true but characterData is false".into(), )); } // Step 7 let add_new_observer = { let mut replaced = false; for registered in &mut *target.registered_mutation_observers_mut() { if &*registered.observer as *const MutationObserver != self as *const MutationObserver { continue; } // TODO: remove matching transient registered observers registered.options.attribute_old_value = attribute_old_value; registered.options.attributes = attributes; registered.options.character_data = character_data; registered.options.character_data_old_value = character_data_old_value; registered.options.child_list = child_list; registered.options.subtree = subtree; registered .options .attribute_filter .clone_from(&attribute_filter); replaced = true; } !replaced }; // Step 8 if add_new_observer { target.add_mutation_observer(RegisteredObserver { observer: DomRoot::from_ref(self), options: ObserverOptions { attributes, attribute_old_value, character_data, character_data_old_value, subtree, attribute_filter, child_list, }, }); self.node_list.borrow_mut().push(DomRoot::from_ref(target)); } Ok(()) } /// fn TakeRecords(&self) -> Vec> { let records: Vec> = self.record_queue.borrow().clone(); self.record_queue.borrow_mut().clear(); records } /// fn Disconnect(&self) { // Step 1 let mut nodes = self.node_list.borrow_mut(); for node in nodes.drain(..) { node.remove_mutation_observer(self); } // Step 2 self.record_queue.borrow_mut().clear(); } }