diff options
author | yvt <i@yvt.jp> | 2021-07-10 17:24:27 +0900 |
---|---|---|
committer | yvt <i@yvt.jp> | 2021-07-10 17:55:42 +0900 |
commit | 01a7de50ab1843d85295f9dccad7f4c099e7208c (patch) | |
tree | ee53fb6e8889deb7b880ee969e6c662e6128d210 /components/script/dom/mutationobserver.rs | |
parent | ff8d2cdbbfc7a9dc7f38b7dd47cb350fde39388f (diff) | |
parent | 94b613fbdaa2b98f2179fc0bbda13c64e6fa0d38 (diff) | |
download | servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.tar.gz servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.zip |
Merge remote-tracking branch 'upstream/master' into feat-cow-infra
`tests/wpt/web-platform-tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html`
was reverted to the upstream version.
Diffstat (limited to 'components/script/dom/mutationobserver.rs')
-rw-r--r-- | components/script/dom/mutationobserver.rs | 351 |
1 files changed, 337 insertions, 14 deletions
diff --git a/components/script/dom/mutationobserver.rs b/components/script/dom/mutationobserver.rs index 4dc745c1ce8..e4141af14a6 100644 --- a/components/script/dom/mutationobserver.rs +++ b/components/script/dom/mutationobserver.rs @@ -1,40 +1,363 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::MutationObserverBinding; -use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationCallback; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::MutationCallback; +use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserverBinding::MutationObserverMethods; +use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserverInit; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::mutationrecord::MutationRecord; +use crate::dom::node::{Node, ShadowIncluding}; +use crate::dom::window::Window; +use crate::microtask::Microtask; +use crate::script_thread::ScriptThread; use dom_struct::dom_struct; -use script_thread::ScriptThread; +use html5ever::{LocalName, Namespace}; use std::rc::Rc; #[dom_struct] pub struct MutationObserver { reflector_: Reflector, - #[ignore_heap_size_of = "can't measure Rc values"] + #[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 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 struct RegisteredObserver { + pub observer: DomRoot<MutationObserver>, + options: ObserverOptions, +} + +#[derive(JSTraceable, MallocSizeOf)] +pub 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(global: &Window, callback: Rc<MutationCallback>) -> Root<MutationObserver> { - let boxed_observer = box MutationObserver::new_inherited(callback); - reflect_dom_object(boxed_observer, global, MutationObserverBinding::Wrap) + fn new(global: &Window, callback: Rc<MutationCallback>) -> DomRoot<MutationObserver> { + let boxed_observer = Box::new(MutationObserver::new_inherited(callback)); + reflect_dom_object(boxed_observer, global) } fn new_inherited(callback: Rc<MutationCallback>) -> MutationObserver { MutationObserver { reflector_: Reflector::new(), callback: callback, + record_queue: DomRefCell::new(vec![]), + node_list: DomRefCell::new(vec![]), } } - pub fn Constructor(global: &Window, callback: Rc<MutationCallback>) -> Fallible<Root<MutationObserver>> { + #[allow(non_snake_case)] + pub fn Constructor( + global: &Window, + callback: Rc<MutationCallback>, + ) -> Fallible<DomRoot<MutationObserver>> { + global.set_exists_mut_observer(); let observer = MutationObserver::new(global, callback); ScriptThread::add_mutation_observer(&*observer); Ok(observer) } + + /// <https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask> + pub fn queue_mutation_observer_microtask() { + // Step 1 + if ScriptThread::is_mutation_observer_microtask_queued() { + return; + } + // Step 2 + ScriptThread::set_mutation_observer_microtask_queued(true); + // Step 3 + ScriptThread::enqueue_microtask(Microtask::NotifyMutationObservers); + } + + /// <https://dom.spec.whatwg.org/#notify-mutation-observers> + pub fn notify_mutation_observers() { + // Step 1 + ScriptThread::set_mutation_observer_microtask_queued(false); + // Step 2 + let notify_list = ScriptThread::get_mutation_observers(); + // TODO: steps 3-4 (slots) + // Step 5 + for mo in ¬ify_list { + let queue: Vec<DomRoot<MutationRecord>> = mo.record_queue.borrow().clone(); + mo.record_queue.borrow_mut().clear(); + // TODO: Step 5.3 Remove all transient registered observers whose observer is mo. + if !queue.is_empty() { + let _ = mo + .callback + .Call_(&**mo, queue, &**mo, ExceptionHandling::Report); + } + } + // TODO: Step 6 (slot signals) + } + + /// <https://dom.spec.whatwg.org/#queueing-a-mutation-record> + pub fn queue_a_mutation_record(target: &Node, attr_type: Mutation) { + 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(|&(ref o, _)| { + &**o as *const _ == &*registered.observer as *const _ + }); + 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(|&(ref o, _)| { + &**o as *const _ == &*registered.observer as *const _ + }); + 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) + }, + Mutation::CharacterData { .. } => { + MutationRecord::character_data_mutated(target, paired_string) + }, + Mutation::ChildList { + ref added, + ref removed, + ref next, + ref prev, + } => MutationRecord::child_list_mutated(target, *added, *removed, *next, *prev), + }; + // Step 4.8 + observer.record_queue.borrow_mut().push(record); + } + + // Step 5 + MutationObserver::queue_mutation_observer_microtask(); + } +} + +impl MutationObserverMethods for MutationObserver { + /// <https://dom.spec.whatwg.org/#dom-mutationobserver-observe> + fn Observe(&self, target: &Node, options: &MutationObserverInit) -> Fallible<()> { + let attribute_filter = options.attributeFilter.clone().unwrap_or(vec![]); + 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 = attribute_filter.clone(); + 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(); + } } |