| /* 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, 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<MutationCallback>, |
| record_queue: DomRefCell<Vec<DomRoot<MutationRecord>>>, |
| node_list: DomRefCell<Vec<DomRoot<Node>>>, |
| } |
| |
| pub(crate) enum Mutation<'a> { |
| Attribute { |
| name: LocalName, |
| namespace: Namespace, |
| old_value: Option<DOMString>, |
| }, |
| 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<MutationObserver>, |
| 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<DOMString>, |
| } |
| |
| impl MutationObserver { |
| fn new_with_proto( |
| global: &Window, |
| proto: Option<HandleObject>, |
| callback: Rc<MutationCallback>, |
| can_gc: CanGc, |
| ) -> DomRoot<MutationObserver> { |
| 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<MutationCallback>) -> MutationObserver { |
| MutationObserver { |
| reflector_: Reflector::new(), |
| callback, |
| record_queue: DomRefCell::new(vec![]), |
| node_list: DomRefCell::new(vec![]), |
| } |
| } |
| |
| /// <https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask> |
| 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); |
| } |
| |
| /// <https://dom.spec.whatwg.org/#notify-mutation-observers> |
| 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<DomRoot<MutationRecord>> = 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::<EventTarget>() |
| .fire_event(atom!("slotchange"), can_gc); |
| } |
| } |
| |
| /// <https://dom.spec.whatwg.org/#queueing-a-mutation-record> |
| pub(crate) fn queue_a_mutation_record<'a, F>( |
| target: &Node, |
| attr_type: LazyCell<Mutation<'a>, F>, |
| ) where |
| F: FnOnce() -> Mutation<'a>, |
| { |
| if !target.global().as_window().get_exists_mut_observer() { |
| return; |
| } |
| // Step 1 |
| let mut interested_observers: Vec<(DomRoot<MutationObserver>, Option<DOMString>)> = 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<crate::DomTypeHolder> for MutationObserver { |
| /// <https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver> |
| fn Constructor( |
| global: &Window, |
| proto: Option<HandleObject>, |
| can_gc: CanGc, |
| callback: Rc<MutationCallback>, |
| ) -> Fallible<DomRoot<MutationObserver>> { |
| global.set_exists_mut_observer(); |
| let observer = MutationObserver::new_with_proto(global, proto, callback, can_gc); |
| ScriptThread::add_mutation_observer(&observer); |
| Ok(observer) |
| } |
| |
| /// <https://dom.spec.whatwg.org/#dom-mutationobserver-observe> |
| 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 !std::ptr::eq(&*registered.observer, self) { |
| 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(()) |
| } |
| |
| /// <https://dom.spec.whatwg.org/#dom-mutationobserver-takerecords> |
| fn TakeRecords(&self) -> Vec<DomRoot<MutationRecord>> { |
| let records: Vec<DomRoot<MutationRecord>> = self.record_queue.borrow().clone(); |
| self.record_queue.borrow_mut().clear(); |
| records |
| } |
| |
| /// <https://dom.spec.whatwg.org/#dom-mutationobserver-disconnect> |
| 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(); |
| } |
| } |