Implement ChildNode::before & ChildNode::after

Continued from #6536

The current implementations of `ChildNode::before` and
`ChildNode::after` do not match the WHATWG spec. This commit updates the
implementations to match the spec.

Our current implementation of `ChildNode::after` passes all the WPT
tests. So I made sure to add a regression test that failed with the
current implementation. There are a few other unit tests I added
to exhaust other corner cases I encountered.
This commit is contained in:
Corey Farwell 2015-07-02 17:23:30 -07:00
parent e74a13c01d
commit 8cfccda542
4 changed files with 139 additions and 41 deletions

View file

@ -801,40 +801,54 @@ impl<'a> NodeHelpers for &'a Node {
// https://dom.spec.whatwg.org/#dom-childnode-before // https://dom.spec.whatwg.org/#dom-childnode-before
fn before(self, nodes: Vec<NodeOrString>) -> ErrorResult { fn before(self, nodes: Vec<NodeOrString>) -> ErrorResult {
match self.parent_node.get() {
None => {
// Step 1. // Step 1.
Ok(()) let parent = &self.parent_node;
},
Some(ref parent_node) => {
// Step 2. // Step 2.
let doc = self.owner_doc(); let parent = match parent.get() {
let node = try!(doc.r().node_from_nodes_and_strings(nodes)); None => return Ok(()),
Some(ref parent) => parent.root(),
};
// Step 3. // Step 3.
Node::pre_insert(node.r(), parent_node.root().r(), let viable_previous_sibling = first_node_not_in(self.preceding_siblings(), &nodes);
Some(self)).map(|_| ())
}, // Step 4.
} let node = try!(self.owner_doc().node_from_nodes_and_strings(nodes));
// Step 5.
let viable_previous_sibling = match viable_previous_sibling {
Some(ref viable_previous_sibling) => viable_previous_sibling.next_sibling.get(),
None => parent.first_child.get(),
}.map(|s| s.root());
// Step 6.
try!(Node::pre_insert(&node, &parent, viable_previous_sibling.r()));
Ok(())
} }
// https://dom.spec.whatwg.org/#dom-childnode-after // https://dom.spec.whatwg.org/#dom-childnode-after
fn after(self, nodes: Vec<NodeOrString>) -> ErrorResult { fn after(self, nodes: Vec<NodeOrString>) -> ErrorResult {
match self.parent_node.get() {
None => {
// Step 1. // Step 1.
Ok(()) let parent = &self.parent_node;
},
Some(ref parent_node) => {
// Step 2. // Step 2.
let doc = self.owner_doc(); let parent = match parent.get() {
let node = try!(doc.r().node_from_nodes_and_strings(nodes)); None => return Ok(()),
Some(ref parent) => parent.root(),
};
// Step 3. // Step 3.
// FIXME(https://github.com/servo/servo/issues/5720) let viable_next_sibling = first_node_not_in(self.following_siblings(), &nodes);
let next_sibling = self.next_sibling.get().map(Root::from_rooted);
Node::pre_insert(node.r(), parent_node.root().r(), // Step 4.
next_sibling.r()).map(|_| ()) let node = try!(self.owner_doc().node_from_nodes_and_strings(nodes));
},
} // Step 5.
try!(Node::pre_insert(&node, &parent, viable_next_sibling.r()));
Ok(())
} }
// https://dom.spec.whatwg.org/#dom-childnode-replacewith // https://dom.spec.whatwg.org/#dom-childnode-replacewith
@ -1027,6 +1041,21 @@ impl<'a> NodeHelpers for &'a Node {
} }
} }
/// Iterate through `nodes` until we find a `Node` that is not in `not_in`
fn first_node_not_in<I>(mut nodes: I, not_in: &[NodeOrString]) -> Option<Root<Node>>
where I: Iterator<Item=Root<Node>>
{
nodes.find(|node| {
not_in.iter().all(|n| {
match n {
&NodeOrString::eNode(ref n) => n != node,
_ => true,
}
})
})
}
/// If the given untrusted node address represents a valid DOM node in the given runtime, /// If the given untrusted node address represents a valid DOM node in the given runtime,
/// returns it. /// returns it.
#[allow(unsafe_code)] #[allow(unsafe_code)]

View file

@ -1,11 +0,0 @@
[ChildNode-before.html]
type: testharness
[Comment.before() with context object itself as the argument.]
expected: FAIL
[Element.before() with context object itself as the argument.]
expected: FAIL
[Text.before() with context object itself as the argument.]
expected: FAIL

View file

@ -72,6 +72,16 @@ function test_after(child, nodeName, innerHTML) {
assert_equals(parent.innerHTML, expected); assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with context object itself as the argument.'); }, nodeName + '.after() with context object itself as the argument.');
test(function() {
var parent = document.createElement('div')
var x = document.createElement('x');
parent.appendChild(x);
parent.appendChild(child);
child.after(child, x);
var expected = innerHTML + '<x></x>';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with context object itself and node as the arguments, switching positions.');
test(function() { test(function() {
var parent = document.createElement('div'); var parent = document.createElement('div');
var x = document.createElement('x'); var x = document.createElement('x');
@ -85,6 +95,36 @@ function test_after(child, nodeName, innerHTML) {
assert_equals(parent.innerHTML, expected); assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with all siblings of child as arguments.'); }, nodeName + '.after() with all siblings of child as arguments.');
test(function() {
var parent = document.createElement('div')
var x = document.createElement('x');
var y = document.createElement('y');
var z = document.createElement('z');
parent.appendChild(child);
parent.appendChild(x);
parent.appendChild(y);
parent.appendChild(z);
child.after(x, y);
var expected = innerHTML + '<x></x><y></y><z></z>';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.before() with some siblings of child as arguments; no changes in tree; viable sibling is first child.');
test(function() {
var parent = document.createElement('div')
var v = document.createElement('v');
var x = document.createElement('x');
var y = document.createElement('y');
var z = document.createElement('z');
parent.appendChild(child);
parent.appendChild(v);
parent.appendChild(x);
parent.appendChild(y);
parent.appendChild(z);
child.after(v, x);
var expected = innerHTML + '<v></v><x></x><y></y><z></z>';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with some siblings of child as arguments; no changes in tree.');
test(function() { test(function() {
var parent = document.createElement('div'); var parent = document.createElement('div');
var x = document.createElement('x'); var x = document.createElement('x');

View file

@ -72,6 +72,16 @@ function test_before(child, nodeName, innerHTML) {
assert_equals(parent.innerHTML, expected); assert_equals(parent.innerHTML, expected);
}, nodeName + '.before() with context object itself as the argument.'); }, nodeName + '.before() with context object itself as the argument.');
test(function() {
var parent = document.createElement('div')
var x = document.createElement('x');
parent.appendChild(child);
parent.appendChild(x);
child.before(x, child);
var expected = '<x></x>' + innerHTML;
assert_equals(parent.innerHTML, expected);
}, nodeName + '.before() with context object itself and node as the arguments, switching positions.');
test(function() { test(function() {
var parent = document.createElement('div'); var parent = document.createElement('div');
var x = document.createElement('x'); var x = document.createElement('x');
@ -85,6 +95,36 @@ function test_before(child, nodeName, innerHTML) {
assert_equals(parent.innerHTML, expected); assert_equals(parent.innerHTML, expected);
}, nodeName + '.before() with all siblings of child as arguments.'); }, nodeName + '.before() with all siblings of child as arguments.');
test(function() {
var parent = document.createElement('div')
var x = document.createElement('x');
var y = document.createElement('y');
var z = document.createElement('z');
parent.appendChild(x);
parent.appendChild(y);
parent.appendChild(z);
parent.appendChild(child);
child.before(y, z);
var expected = '<x></x><y></y><z></z>' + innerHTML;
assert_equals(parent.innerHTML, expected);
}, nodeName + '.before() with some siblings of child as arguments; no changes in tree; viable sibling is first child.');
test(function() {
var parent = document.createElement('div')
var v = document.createElement('v');
var x = document.createElement('x');
var y = document.createElement('y');
var z = document.createElement('z');
parent.appendChild(v);
parent.appendChild(x);
parent.appendChild(y);
parent.appendChild(z);
parent.appendChild(child);
child.before(y, z);
var expected = '<v></v><x></x><y></y><z></z>' + innerHTML;
assert_equals(parent.innerHTML, expected);
}, nodeName + '.before() with some siblings of child as arguments; no changes in tree.');
test(function() { test(function() {
var parent = document.createElement('div'); var parent = document.createElement('div');
var x = document.createElement('x'); var x = document.createElement('x');