MutationObserver api: Implement takeRecords() and characterData mutations

This commit is contained in:
Fabrice Desré 2018-04-24 20:39:28 -07:00
parent c5f7c9ccf3
commit 75434e6fce
9 changed files with 98 additions and 112 deletions

View file

@ -185,7 +185,7 @@ impl Attr {
let mutation = Mutation::Attribute {
name: name.clone(),
namespace: namespace.clone(),
old_value: old_value.clone(),
old_value: Some(old_value.clone()),
};
MutationObserver::queue_a_mutation_record(owner.upcast::<Node>(), mutation);

View file

@ -17,6 +17,7 @@ use dom::bindings::str::DOMString;
use dom::comment::Comment;
use dom::document::Document;
use dom::element::Element;
use dom::mutationobserver::{Mutation, MutationObserver};
use dom::node::{ChildrenMutation, Node, NodeDamage};
use dom::processinginstruction::ProcessingInstruction;
use dom::text::Text;
@ -81,6 +82,14 @@ impl CharacterData {
}
}
}
// Queue a MutationObserver record before changing the content.
fn queue_mutation_record(&self) {
let mutation = Mutation::CharacterData {
old_value: self.data.borrow().clone(),
};
MutationObserver::queue_a_mutation_record(self.upcast::<Node>(), mutation);
}
}
impl CharacterDataMethods for CharacterData {
@ -91,6 +100,7 @@ impl CharacterDataMethods for CharacterData {
// https://dom.spec.whatwg.org/#dom-characterdata-data
fn SetData(&self, data: DOMString) {
self.queue_mutation_record();
let old_length = self.Length();
let new_length = data.encode_utf16().count() as u32;
*self.data.borrow_mut() = data;
@ -193,6 +203,8 @@ impl CharacterDataMethods for CharacterData {
}
};
// Step 4: Mutation observers.
self.queue_mutation_record();
// Step 5 to 7.
new_data = String::with_capacity(
prefix.len() +

View file

@ -1163,16 +1163,16 @@ impl Element {
pub fn push_attribute(&self, attr: &Attr) {
let name = attr.local_name().clone();
let namespace = attr.namespace().clone();
let value = DOMString::from(&**attr.value());
let mutation = Mutation::Attribute {
name: name.clone(),
namespace: namespace.clone(),
old_value: value.clone(),
old_value: None,
};
MutationObserver::queue_a_mutation_record(&self.node, mutation);
if self.get_custom_element_definition().is_some() {
let value = DOMString::from(&**attr.value());
let reaction = CallbackReaction::AttributeChanged(name, None, Some(value), namespace);
ScriptThread::enqueue_callback_reaction(self, reaction, None);
}
@ -1311,7 +1311,7 @@ impl Element {
let mutation = Mutation::Attribute {
name: name.clone(),
namespace: namespace.clone(),
old_value: old_value.clone(),
old_value: Some(old_value.clone()),
};
MutationObserver::queue_a_mutation_record(&self.node, mutation);

View file

@ -30,7 +30,8 @@ pub struct MutationObserver {
}
pub enum Mutation<'a> {
Attribute { name: LocalName, namespace: Namespace, old_value: DOMString },
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> },
}
@ -110,7 +111,8 @@ impl MutationObserver {
return;
}
// Step 1
let mut interestedObservers: Vec<(DomRoot<MutationObserver>, Option<DOMString>)> = vec![];
let mut interested_observers: Vec<(DomRoot<MutationObserver>, Option<DOMString>)> = vec![];
// Step 2 & 3
for node in target.inclusive_ancestors() {
for registered in &*node.registered_mutation_observers() {
@ -135,32 +137,52 @@ impl MutationObserver {
}
// 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 = interestedObservers.iter().position(|&(ref o, _)|
let idx = interested_observers.iter().position(|&(ref o, _)|
&**o as *const _ == &*registered.observer as *const _);
if let Some(idx) = idx {
interestedObservers[idx].1 = paired_string;
interested_observers[idx].1 = paired_string;
} else {
interestedObservers.push((DomRoot::from_ref(&*registered.observer),
paired_string));
interested_observers.push((DomRoot::from_ref(&*registered.observer),
paired_string));
}
},
Mutation::ChildList { .. } => {
if !registered.options.child_list {
continue;
}
interestedObservers.push((DomRoot::from_ref(&*registered.observer), None));
interested_observers.push((DomRoot::from_ref(&*registered.observer), None));
}
}
}
}
// Step 4
for &(ref observer, ref paired_string) in &interestedObservers {
for &(ref observer, ref paired_string) in &interested_observers {
// Steps 4.1-4.7
let record = match attr_type {
Mutation::Attribute { ref name, ref namespace, .. } => {
@ -171,6 +193,9 @@ impl MutationObserver {
};
MutationRecord::attribute_mutated(target, name, namespace, paired_string.clone())
},
Mutation::CharacterData { .. } => {
MutationRecord::character_data_mutated(target, paired_string.clone())
}
Mutation::ChildList { ref added, ref removed, ref next, ref prev } => {
MutationRecord::child_list_mutated(target, *added, *removed, *next, *prev)
}
@ -265,4 +290,11 @@ impl MutationObserverMethods for MutationObserver {
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
}
}

View file

@ -28,10 +28,12 @@ pub struct MutationRecord {
impl MutationRecord {
#[allow(unrooted_must_root)]
pub fn attribute_mutated(target: &Node,
attribute_name: &LocalName,
attribute_namespace: Option<&Namespace>,
old_value: Option<DOMString>) -> DomRoot<MutationRecord> {
pub fn attribute_mutated(
target: &Node,
attribute_name: &LocalName,
attribute_namespace: Option<&Namespace>,
old_value: Option<DOMString>,
) -> DomRoot<MutationRecord> {
let record = Box::new(MutationRecord::new_inherited(
"attributes",
target,
@ -43,11 +45,30 @@ impl MutationRecord {
reflect_dom_object(record, &*window_from_node(target), MutationRecordBinding::Wrap)
}
pub fn child_list_mutated(target: &Node,
added_nodes: Option<&[&Node]>,
removed_nodes: Option<&[&Node]>,
next_sibling: Option<&Node>,
prev_sibling: Option<&Node>) -> DomRoot<MutationRecord> {
pub fn character_data_mutated(
target: &Node,
old_value: Option<DOMString>,
) -> DomRoot<MutationRecord> {
reflect_dom_object(
Box::new(MutationRecord::new_inherited(
"characterData",
target,
None, None,
old_value,
None, None, None, None
)),
&*window_from_node(target),
MutationRecordBinding::Wrap
)
}
pub fn child_list_mutated(
target: &Node,
added_nodes: Option<&[&Node]>,
removed_nodes: Option<&[&Node]>,
next_sibling: Option<&Node>,
prev_sibling: Option<&Node>,
) -> DomRoot<MutationRecord> {
let window = window_from_node(target);
let added_nodes = added_nodes.map(|list| NodeList::new_simple_list_slice(&window, list));
let removed_nodes = removed_nodes.map(|list| NodeList::new_simple_list_slice(&window, list));
@ -67,15 +88,17 @@ impl MutationRecord {
)
}
fn new_inherited(record_type: &str,
target: &Node,
attribute_name: Option<DOMString>,
attribute_namespace: Option<DOMString>,
old_value: Option<DOMString>,
added_nodes: Option<&NodeList>,
removed_nodes: Option<&NodeList>,
next_sibling: Option<&Node>,
prev_sibling: Option<&Node>) -> MutationRecord {
fn new_inherited(
record_type: &str,
target: &Node,
attribute_name: Option<DOMString>,
attribute_namespace: Option<DOMString>,
old_value: Option<DOMString>,
added_nodes: Option<&NodeList>,
removed_nodes: Option<&NodeList>,
next_sibling: Option<&Node>,
prev_sibling: Option<&Node>,
) -> MutationRecord {
MutationRecord {
reflector_: Reflector::new(),
record_type: DOMString::from(record_type),

View file

@ -12,7 +12,7 @@ interface MutationObserver {
[Throws]
void observe(Node target, optional MutationObserverInit options);
//void disconnect();
//sequence<MutationRecord> takeRecords();
sequence<MutationRecord> takeRecords();
};
callback MutationCallback = void (sequence<MutationRecord> mutations, MutationObserver observer);

View file

@ -1,28 +1,5 @@
[MutationObserver-attributes.html]
type: testharness
[attributes Element.id: same value mutation]
expected: FAIL
[attributes Element.className: new value mutation]
expected: FAIL
[attributes Element.classList.add: single token addition mutation]
expected: FAIL
[attributes Element.classList.add: multiple tokens addition mutation]
expected: FAIL
[attributes Element.setAttribute: classname mutation]
expected: FAIL
[attributes Element.setAttributeNS: creation mutation]
expected: FAIL
[attributes Element.setAttributeNS: prefixed attribute creation mutation]
expected: FAIL
[attributes Element.className: empty string update mutation]
expected: FAIL
[attributes/attributeFilter Element.id/Element.className: update mutation]
expected: FAIL
@ -32,4 +9,3 @@
[attributeFilter alone Element.id/Element.className: multiple filter update mutation]
expected: FAIL

View file

@ -1,11 +1,6 @@
[MutationObserver-characterData.html]
type: testharness
expected: TIMEOUT
[characterData Text.data: simple mutation without oldValue]
expected: TIMEOUT
[characterData Text.data: simple mutation]
expected: TIMEOUT
[characterData Text.appendData: simple mutation]
expected: TIMEOUT
@ -15,46 +10,3 @@
[characterData Text.appendData: null string mutation]
expected: TIMEOUT
[characterData Text.insertData: simple mutation]
expected: TIMEOUT
[characterData Text.insertData: empty string mutation]
expected: TIMEOUT
[characterData Text.insertData: null string mutation]
expected: TIMEOUT
[characterData Text.deleteData: simple mutation]
expected: TIMEOUT
[characterData Text.deleteData: empty mutation]
expected: TIMEOUT
[characterData Text.replaceData: simple mutation]
expected: TIMEOUT
[characterData Text.replaceData: empty mutation]
expected: TIMEOUT
[characterData ProcessingInstruction: data mutations]
expected: TIMEOUT
[characterData Comment: data mutations]
expected: TIMEOUT
[characterData Range.deleteContents: child and data removal mutation]
expected: TIMEOUT
[characterData Range.deleteContents: child and data removal mutation (2)]
expected: TIMEOUT
[characterData Range.extractContents: child and data removal mutation]
expected: TIMEOUT
[characterData Range.extractContents: child and data removal mutation (2)]
expected: TIMEOUT
[characterData/characterDataOldValue alone Text.data: simple mutation]
expected: TIMEOUT

View file

@ -1,11 +1,2 @@
[MutationObserver-takeRecords.html]
type: testharness
[unreachabled test]
expected: FAIL
[All records present]
expected: FAIL
[No more records present]
expected: FAIL