mirror of
https://github.com/servo/servo.git
synced 2025-08-07 06:25:32 +01:00
Mutation Observer API
This commit is contained in:
parent
fa251ec96b
commit
3ca89204ff
15 changed files with 432 additions and 180 deletions
|
@ -2,13 +2,22 @@
|
|||
* 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::callback::ExceptionHandling;
|
||||
use dom::bindings::cell::DOMRefCell;
|
||||
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::codegen::Bindings::MutationObserverBinding::MutationObserverBinding::MutationObserverMethods;
|
||||
use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserverInit;
|
||||
use dom::bindings::error::{Error, Fallible};
|
||||
use dom::bindings::js::{JS, Root};
|
||||
use dom::bindings::reflector::{Reflector, reflect_dom_object};
|
||||
use dom::bindings::str::DOMString;
|
||||
use dom::mutationrecord::MutationRecord;
|
||||
use dom::node::Node;
|
||||
use dom::window::Window;
|
||||
use dom_struct::dom_struct;
|
||||
use html5ever::{Namespace, LocalName};
|
||||
use microtask::Microtask;
|
||||
use script_thread::ScriptThread;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -17,6 +26,29 @@ pub struct MutationObserver {
|
|||
reflector_: Reflector,
|
||||
#[ignore_heap_size_of = "can't measure Rc values"]
|
||||
callback: Rc<MutationCallback>,
|
||||
record_queue: DOMRefCell<Vec<Root<MutationRecord>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Mutation {
|
||||
Attribute { name: LocalName, namespace: Namespace, old_value: DOMString }
|
||||
}
|
||||
|
||||
#[derive(HeapSizeOf, JSTraceable)]
|
||||
pub struct RegisteredObserver {
|
||||
observer: Root<MutationObserver>,
|
||||
options: ObserverOptions,
|
||||
}
|
||||
|
||||
#[derive(HeapSizeOf, JSTraceable)]
|
||||
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 {
|
||||
|
@ -29,6 +61,7 @@ impl MutationObserver {
|
|||
MutationObserver {
|
||||
reflector_: Reflector::new(),
|
||||
callback: callback,
|
||||
record_queue: DOMRefCell::new(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,4 +70,185 @@ impl MutationObserver {
|
|||
ScriptThread::add_mutation_observer(&*observer);
|
||||
Ok(observer)
|
||||
}
|
||||
|
||||
/// https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask
|
||||
pub fn queue_mutation_observer_compound_microtask() {
|
||||
// Step 1
|
||||
if ScriptThread::is_mutation_observer_compound_microtask_queued() {
|
||||
return;
|
||||
}
|
||||
// Step 2
|
||||
ScriptThread::set_mutation_observer_compound_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_compound_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<Root<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) {
|
||||
// Step 1
|
||||
let mut interestedObservers: Vec<(Root<MutationObserver>, Option<DOMString>)> = vec![];
|
||||
// Step 2 & 3
|
||||
for node in target.inclusive_ancestors() {
|
||||
for registered in &*node.registered_mutation_observers() {
|
||||
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()
|
||||
.find(|s| &**s == &**name).is_some() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Step 3.1.2
|
||||
let paired_string = if registered.options.attribute_old_value {
|
||||
Some(old_value.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Step 3.1.1
|
||||
let idx = interestedObservers.iter().position(|&(ref o, _)|
|
||||
&**o as *const _ == &*registered.observer as *const _);
|
||||
if let Some(idx) = idx {
|
||||
interestedObservers[idx].1 = paired_string;
|
||||
} else {
|
||||
interestedObservers.push((Root::from_ref(&*registered.observer),
|
||||
paired_string));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4
|
||||
for &(ref observer, ref paired_string) in &interestedObservers {
|
||||
// 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.clone())
|
||||
}
|
||||
};
|
||||
// Step 4.8
|
||||
observer.record_queue.borrow_mut().push(record);
|
||||
}
|
||||
|
||||
// Step 5
|
||||
MutationObserver::queue_mutation_observer_compound_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() {
|
||||
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.registered_mutation_observers().push(RegisteredObserver {
|
||||
observer: Root::from_ref(self),
|
||||
options: ObserverOptions {
|
||||
attributes,
|
||||
attribute_old_value,
|
||||
character_data,
|
||||
character_data_old_value,
|
||||
subtree,
|
||||
attribute_filter,
|
||||
child_list
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue