Auto merge of #16933 - MortimerGoro:mutation_children, r=jdm

Implement MutationObserver childList mutations.

<!-- Please describe your changes on the following line: -->

Implement MutationObserver childList mutations

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [ ] These changes fix #__ (github issue number if applicable).

<!-- Either: -->
- [x] There are tests for these changes OR
- [ ] These changes do not require tests because _____

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16933)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-05-20 15:04:43 -05:00 committed by GitHub
commit 323760f47e
6 changed files with 112 additions and 119 deletions

View file

@ -29,9 +29,10 @@ pub struct MutationObserver {
record_queue: DOMRefCell<Vec<Root<MutationRecord>>>, record_queue: DOMRefCell<Vec<Root<MutationRecord>>>,
} }
#[derive(Clone)] pub enum Mutation<'a> {
pub enum Mutation { Attribute { name: LocalName, namespace: Namespace, old_value: DOMString },
Attribute { name: LocalName, namespace: Namespace, old_value: DOMString } ChildList { added: Option<&'a [&'a Node]>, removed: Option<&'a [&'a Node]>,
prev: Option<&'a Node>, next: Option<&'a Node> },
} }
#[derive(HeapSizeOf, JSTraceable)] #[derive(HeapSizeOf, JSTraceable)]
@ -143,6 +144,12 @@ impl MutationObserver {
interestedObservers.push((Root::from_ref(&*registered.observer), interestedObservers.push((Root::from_ref(&*registered.observer),
paired_string)); paired_string));
} }
},
Mutation::ChildList { .. } => {
if !registered.options.child_list {
continue;
}
interestedObservers.push((Root::from_ref(&*registered.observer), None));
} }
} }
} }
@ -159,6 +166,9 @@ impl MutationObserver {
None None
}; };
MutationRecord::attribute_mutated(target, name, namespace, paired_string.clone()) MutationRecord::attribute_mutated(target, name, namespace, paired_string.clone())
},
Mutation::ChildList { ref added, ref removed, ref next, ref prev } => {
MutationRecord::child_list_mutated(target, *added, *removed, *next, *prev)
} }
}; };
// Step 4.8 // Step 4.8

View file

@ -4,7 +4,7 @@
use dom::bindings::codegen::Bindings::MutationRecordBinding::MutationRecordBinding; use dom::bindings::codegen::Bindings::MutationRecordBinding::MutationRecordBinding;
use dom::bindings::codegen::Bindings::MutationRecordBinding::MutationRecordBinding::MutationRecordMethods; use dom::bindings::codegen::Bindings::MutationRecordBinding::MutationRecordBinding::MutationRecordMethods;
use dom::bindings::js::{JS, Root}; use dom::bindings::js::{JS, MutNullableJS, Root};
use dom::bindings::reflector::{Reflector, reflect_dom_object}; use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::str::DOMString; use dom::bindings::str::DOMString;
use dom::node::{Node, window_from_node}; use dom::node::{Node, window_from_node};
@ -20,6 +20,10 @@ pub struct MutationRecord {
attribute_name: Option<DOMString>, attribute_name: Option<DOMString>,
attribute_namespace: Option<DOMString>, attribute_namespace: Option<DOMString>,
old_value: Option<DOMString>, old_value: Option<DOMString>,
added_nodes: MutNullableJS<NodeList>,
removed_nodes: MutNullableJS<NodeList>,
next_sibling: Option<JS<Node>>,
prev_sibling: Option<JS<Node>>,
} }
impl MutationRecord { impl MutationRecord {
@ -32,15 +36,40 @@ impl MutationRecord {
target, target,
Some(DOMString::from(&**attribute_name)), Some(DOMString::from(&**attribute_name)),
attribute_namespace.map(|n| DOMString::from(&**n)), attribute_namespace.map(|n| DOMString::from(&**n)),
old_value); old_value,
None, None, None, None);
reflect_dom_object(record, &*window_from_node(target), MutationRecordBinding::Wrap) 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>) -> Root<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));
reflect_dom_object(box MutationRecord::new_inherited("childList",
target,
None, None, None,
added_nodes.as_ref().map(|list| &**list),
removed_nodes.as_ref().map(|list| &**list),
next_sibling,
prev_sibling),
&*window,
MutationRecordBinding::Wrap)
}
fn new_inherited(record_type: &str, fn new_inherited(record_type: &str,
target: &Node, target: &Node,
attribute_name: Option<DOMString>, attribute_name: Option<DOMString>,
attribute_namespace: Option<DOMString>, attribute_namespace: Option<DOMString>,
old_value: Option<DOMString>) -> MutationRecord { old_value: Option<DOMString>,
added_nodes: Option<&NodeList>,
removed_nodes: Option<&NodeList>,
next_sibling: Option<&Node>,
prev_sibling: Option<&Node>) -> MutationRecord {
MutationRecord { MutationRecord {
reflector_: Reflector::new(), reflector_: Reflector::new(),
record_type: DOMString::from(record_type), record_type: DOMString::from(record_type),
@ -48,6 +77,10 @@ impl MutationRecord {
attribute_name: attribute_name, attribute_name: attribute_name,
attribute_namespace: attribute_namespace, attribute_namespace: attribute_namespace,
old_value: old_value, old_value: old_value,
added_nodes: MutNullableJS::new(added_nodes),
removed_nodes: MutNullableJS::new(removed_nodes),
next_sibling: next_sibling.map(JS::from_ref),
prev_sibling: prev_sibling.map(JS::from_ref),
} }
} }
} }
@ -80,24 +113,28 @@ impl MutationRecordMethods for MutationRecord {
// https://dom.spec.whatwg.org/#dom-mutationrecord-addednodes // https://dom.spec.whatwg.org/#dom-mutationrecord-addednodes
fn AddedNodes(&self) -> Root<NodeList> { fn AddedNodes(&self) -> Root<NodeList> {
let window = window_from_node(&*self.target); self.added_nodes.or_init(|| {
NodeList::empty(&window) let window = window_from_node(&*self.target);
NodeList::empty(&window)
})
} }
// https://dom.spec.whatwg.org/#dom-mutationrecord-removednodes // https://dom.spec.whatwg.org/#dom-mutationrecord-removednodes
fn RemovedNodes(&self) -> Root<NodeList> { fn RemovedNodes(&self) -> Root<NodeList> {
let window = window_from_node(&*self.target); self.removed_nodes.or_init(|| {
NodeList::empty(&window) let window = window_from_node(&*self.target);
NodeList::empty(&window)
})
} }
// https://dom.spec.whatwg.org/#dom-mutationrecord-previoussibling // https://dom.spec.whatwg.org/#dom-mutationrecord-previoussibling
fn GetPreviousSibling(&self) -> Option<Root<Node>> { fn GetPreviousSibling(&self) -> Option<Root<Node>> {
None self.prev_sibling.as_ref().map(|node| Root::from_ref(&**node))
} }
// https://dom.spec.whatwg.org/#dom-mutationrecord-previoussibling // https://dom.spec.whatwg.org/#dom-mutationrecord-previoussibling
fn GetNextSibling(&self) -> Option<Root<Node>> { fn GetNextSibling(&self) -> Option<Root<Node>> {
None self.next_sibling.as_ref().map(|node| Root::from_ref(&**node))
} }
} }

View file

@ -47,7 +47,7 @@ use dom::htmllinkelement::HTMLLinkElement;
use dom::htmlmetaelement::HTMLMetaElement; use dom::htmlmetaelement::HTMLMetaElement;
use dom::htmlstyleelement::HTMLStyleElement; use dom::htmlstyleelement::HTMLStyleElement;
use dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; use dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers};
use dom::mutationobserver::RegisteredObserver; use dom::mutationobserver::{Mutation, MutationObserver, RegisteredObserver};
use dom::nodelist::NodeList; use dom::nodelist::NodeList;
use dom::processinginstruction::ProcessingInstruction; use dom::processinginstruction::ProcessingInstruction;
use dom::range::WeakRangeVec; use dom::range::WeakRangeVec;
@ -1616,18 +1616,27 @@ impl Node {
let new_nodes = if let NodeTypeId::DocumentFragment = node.type_id() { let new_nodes = if let NodeTypeId::DocumentFragment = node.type_id() {
// Step 3. // Step 3.
new_nodes.extend(node.children().map(|kid| JS::from_ref(&*kid))); new_nodes.extend(node.children().map(|kid| JS::from_ref(&*kid)));
// Step 4: mutation observers. // Step 4.
// Step 5.
for kid in new_nodes.r() { for kid in new_nodes.r() {
Node::remove(*kid, node, SuppressObserver::Suppressed); Node::remove(*kid, node, SuppressObserver::Suppressed);
} }
// Step 5.
vtable_for(&node).children_changed(&ChildrenMutation::replace_all(new_nodes.r(), &[])); vtable_for(&node).children_changed(&ChildrenMutation::replace_all(new_nodes.r(), &[]));
let mutation = Mutation::ChildList {
added: None,
removed: Some(new_nodes.r()),
prev: None,
next: None,
};
MutationObserver::queue_a_mutation_record(&node, mutation);
new_nodes.r() new_nodes.r()
} else { } else {
// Step 3. // Step 3.
ref_slice(&node) ref_slice(&node)
}; };
// Step 6: mutation observers. // Step 6.
let previous_sibling = match suppress_observers { let previous_sibling = match suppress_observers {
SuppressObserver::Unsuppressed => { SuppressObserver::Unsuppressed => {
match child { match child {
@ -1646,6 +1655,14 @@ impl Node {
if let SuppressObserver::Unsuppressed = suppress_observers { if let SuppressObserver::Unsuppressed = suppress_observers {
vtable_for(&parent).children_changed( vtable_for(&parent).children_changed(
&ChildrenMutation::insert(previous_sibling.r(), new_nodes, child)); &ChildrenMutation::insert(previous_sibling.r(), new_nodes, child));
let mutation = Mutation::ChildList {
added: Some(new_nodes),
removed: None,
prev: previous_sibling.r(),
next: child,
};
MutationObserver::queue_a_mutation_record(&parent, mutation);
} }
} }
@ -1677,9 +1694,19 @@ impl Node {
if let Some(node) = node { if let Some(node) = node {
Node::insert(node, parent, None, SuppressObserver::Suppressed); Node::insert(node, parent, None, SuppressObserver::Suppressed);
} }
// Step 6: mutation observers. // Step 6.
vtable_for(&parent).children_changed( vtable_for(&parent).children_changed(
&ChildrenMutation::replace_all(removed_nodes.r(), added_nodes)); &ChildrenMutation::replace_all(removed_nodes.r(), added_nodes));
if !removed_nodes.is_empty() || !added_nodes.is_empty() {
let mutation = Mutation::ChildList {
added: Some(added_nodes),
removed: Some(removed_nodes.r()),
prev: None,
next: None,
};
MutationObserver::queue_a_mutation_record(&parent, mutation);
}
} }
// https://dom.spec.whatwg.org/#concept-node-pre-remove // https://dom.spec.whatwg.org/#concept-node-pre-remove
@ -1730,6 +1757,15 @@ impl Node {
&ChildrenMutation::replace(old_previous_sibling.r(), &ChildrenMutation::replace(old_previous_sibling.r(),
&Some(&node), &[], &Some(&node), &[],
old_next_sibling.r())); old_next_sibling.r()));
let removed = [node];
let mutation = Mutation::ChildList {
added: None,
removed: Some(&removed),
prev: old_previous_sibling.r(),
next: old_next_sibling.r(),
};
MutationObserver::queue_a_mutation_record(&parent, mutation);
} }
} }
@ -2182,6 +2218,14 @@ impl NodeMethods for Node {
&ChildrenMutation::replace(previous_sibling.r(), &ChildrenMutation::replace(previous_sibling.r(),
&removed_child, nodes, &removed_child, nodes,
reference_child)); reference_child));
let removed = removed_child.map(|r| [r]);
let mutation = Mutation::ChildList {
added: Some(nodes),
removed: removed.as_ref().map(|r| &r[..]),
prev: previous_sibling.r(),
next: reference_child,
};
MutationObserver::queue_a_mutation_record(&self, mutation);
// Step 15. // Step 15.
Ok(Root::from_ref(child)) Ok(Root::from_ref(child))

View file

@ -47,6 +47,10 @@ impl NodeList {
NodeList::new(window, NodeListType::Simple(iter.map(|r| JS::from_ref(&*r)).collect())) NodeList::new(window, NodeListType::Simple(iter.map(|r| JS::from_ref(&*r)).collect()))
} }
pub fn new_simple_list_slice(window: &Window, slice: &[&Node]) -> Root<NodeList> {
NodeList::new(window, NodeListType::Simple(slice.iter().map(|r| JS::from_ref(*r)).collect()))
}
pub fn new_child_list(window: &Window, node: &Node) -> Root<NodeList> { pub fn new_child_list(window: &Window, node: &Node) -> Root<NodeList> {
NodeList::new(window, NodeListType::Children(ChildrenList::new(node))) NodeList::new(window, NodeListType::Children(ChildrenList::new(node)))
} }

View file

@ -1,90 +0,0 @@
[MutationObserver-childList.html]
type: testharness
expected: TIMEOUT
[childList Node.textContent: replace content mutation]
expected: TIMEOUT
[childList Node.textContent: no previous content mutation]
expected: TIMEOUT
[childList Node.textContent: empty string mutation]
expected: TIMEOUT
[childList Node.normalize mutation]
expected: TIMEOUT
[childList Node.normalize mutations]
expected: TIMEOUT
[childList Node.insertBefore: addition mutation]
expected: TIMEOUT
[childList Node.insertBefore: removal mutation]
expected: TIMEOUT
[childList Node.insertBefore: removal and addition mutations]
expected: TIMEOUT
[childList Node.insertBefore: fragment addition mutations]
expected: TIMEOUT
[childList Node.insertBefore: fragment removal mutations]
expected: TIMEOUT
[childList Node.insertBefore: last child addition mutation]
expected: TIMEOUT
[childList Node.appendChild: addition mutation]
expected: TIMEOUT
[childList Node.appendChild: removal mutation]
expected: TIMEOUT
[childList Node.appendChild: removal and addition mutations]
expected: TIMEOUT
[childList Node.appendChild: fragment addition mutations]
expected: TIMEOUT
[childList Node.appendChild: fragment removal mutations]
expected: TIMEOUT
[childList Node.appendChild: addition outside document tree mutation]
expected: TIMEOUT
[childList Node.replaceChild: replacement mutation]
expected: TIMEOUT
[childList Node.replaceChild: removal mutation]
expected: TIMEOUT
[childList Node.replaceChild: internal replacement mutation]
expected: TIMEOUT
[childList Node.removeChild: removal mutation]
expected: TIMEOUT
[childList Range.deleteContents: child removal mutation]
expected: TIMEOUT
[childList Range.deleteContents: child and data removal mutation]
expected: TIMEOUT
[childList Range.extractContents: child removal mutation]
expected: TIMEOUT
[childList Range.extractContents: child and data removal mutation]
expected: TIMEOUT
[childList Range.insertNode: child insertion mutation]
expected: TIMEOUT
[childList Range.insertNode: children insertion mutation]
expected: TIMEOUT
[childList Range.surroundContents: children removal and addition mutation]
expected: TIMEOUT
[childList Node.replaceChild: self internal replacement mutation]
expected: TIMEOUT

View file

@ -1,12 +0,0 @@
[MutationObserver-inner-outer.html]
type: testharness
expected: TIMEOUT
[innerHTML mutation]
expected: FAIL
[innerHTML with 2 children mutation]
expected: TIMEOUT
[outerHTML mutation]
expected: TIMEOUT