Skip to content

Commit

Permalink
auto merge of #1504 : brunoabinader/servo/node-replace-child, r=Ms2ger
Browse files Browse the repository at this point in the history
Implements Node:replaceChild() according to spec below:
http://dom.spec.whatwg.org/#concept-node-replace

Closes #1430.
  • Loading branch information
bors-servo committed Jan 16, 2014
2 parents 9b74250 + 3b82b11 commit ecaaf4c
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 57 deletions.
1 change: 1 addition & 0 deletions src/components/script/dom/bindings/codegen/Bindings.conf
Expand Up @@ -312,6 +312,7 @@ DOMInterfaces = {
'pointerType': '',
'needsAbstract': [
'appendChild',
'replaceChild',
'nodeName',
'nodeValue',
'removeChild',
Expand Down
226 changes: 171 additions & 55 deletions src/components/script/dom/node.rs
Expand Up @@ -258,6 +258,37 @@ impl AbstractNode {
AbstractNode::from_box(boxed_node)
}
}

pub fn is_inclusive_ancestor_of(&self, parent: AbstractNode) -> bool {
*self == parent || parent.ancestors().any(|ancestor| ancestor == *self)
}

pub fn is_parent_of(&self, child: AbstractNode) -> bool {
child.parent_node() == Some(*self)
}

fn followed_by_doctype(child: AbstractNode) -> bool {
let mut iter = child;
loop {
match iter.next_sibling() {
Some(sibling) => {
if sibling.is_doctype() {
return true;
}
iter = sibling;
},
None => return false
}
}
}

fn inclusively_followed_by_doctype(child: Option<AbstractNode>) -> bool {
match child {
Some(child) if child.is_doctype() => true,
Some(child) => AbstractNode::followed_by_doctype(child),
None => false
}
}
}

impl<'a> AbstractNode {
Expand Down Expand Up @@ -528,7 +559,7 @@ impl AbstractNode {
}

pub fn ReplaceChild(self, node: AbstractNode, child: AbstractNode) -> Fallible<AbstractNode> {
self.mut_node().ReplaceChild(node, child)
self.node().ReplaceChild(self, node, child)
}

pub fn RemoveChild(self, node: AbstractNode) -> Fallible<AbstractNode> {
Expand Down Expand Up @@ -1041,87 +1072,49 @@ impl Node {
// http://dom.spec.whatwg.org/#concept-node-pre-insert
fn pre_insert(node: AbstractNode, parent: AbstractNode, child: Option<AbstractNode>)
-> Fallible<AbstractNode> {
fn is_inclusive_ancestor_of(node: AbstractNode, parent: AbstractNode) -> bool {
node == parent || parent.ancestors().any(|ancestor| ancestor == node)
}

// Step 1.
match parent.type_id() {
DocumentNodeTypeId(..) |
DocumentFragmentNodeTypeId |
ElementNodeTypeId(..) => (),
_ => {
return Err(HierarchyRequest);
},
_ => return Err(HierarchyRequest)
}

// Step 2.
if is_inclusive_ancestor_of(node, parent) {
if node.is_inclusive_ancestor_of(parent) {
return Err(HierarchyRequest);
}

// Step 3.
match child {
Some(child) => {
if child.parent_node() != Some(parent) {
return Err(NotFound);
}
},
None => (),
Some(child) if !parent.is_parent_of(child) => return Err(NotFound),
_ => ()
}

// Step 4.
match node.type_id() {
DocumentFragmentNodeTypeId |
DoctypeNodeTypeId |
ElementNodeTypeId(_) |
TextNodeTypeId |
// ProcessingInstructionNodeTypeId |
CommentNodeTypeId => (),
DocumentNodeTypeId(..) => return Err(HierarchyRequest),
}

// Step 5.
// Step 4-5.
match node.type_id() {
TextNodeTypeId => {
match node.parent_node() {
Some(parent) if parent.is_document() => return Err(HierarchyRequest),
_ => ()
}
},
}
DoctypeNodeTypeId => {
match node.parent_node() {
Some(parent) if !parent.is_document() => return Err(HierarchyRequest),
_ => ()
}
},
_ => (),
}
DocumentFragmentNodeTypeId |
ElementNodeTypeId(_) |
// ProcessingInstructionNodeTypeId |
CommentNodeTypeId => (),
DocumentNodeTypeId(..) => return Err(HierarchyRequest)
}

// Step 6.
match parent.type_id() {
DocumentNodeTypeId(_) => {
fn inclusively_followed_by_doctype(child: Option<AbstractNode>) -> bool{
match child {
Some(child) if child.is_doctype() => true,
Some(child) => {
let mut iter = child;
loop {
match iter.next_sibling() {
Some(sibling) => {
if sibling.is_doctype() {
return true;
}
iter = sibling;
},
None => return false,
}
}
},
None => false,
}
}

match node.type_id() {
// Step 6.1
DocumentFragmentNodeTypeId => {
Expand All @@ -1138,7 +1131,7 @@ impl Node {
if parent.child_elements().len() > 0 {
return Err(HierarchyRequest);
}
if inclusively_followed_by_doctype(child) {
if AbstractNode::inclusively_followed_by_doctype(child) {
return Err(HierarchyRequest);
}
},
Expand All @@ -1153,7 +1146,7 @@ impl Node {
if parent.child_elements().len() > 0 {
return Err(HierarchyRequest);
}
if inclusively_followed_by_doctype(child) {
if AbstractNode::inclusively_followed_by_doctype(child) {
return Err(HierarchyRequest);
}
},
Expand Down Expand Up @@ -1362,9 +1355,132 @@ impl Node {
Node::pre_insert(node, abstract_self, None)
}

pub fn ReplaceChild(&mut self, _node: AbstractNode, _child: AbstractNode)
// http://dom.spec.whatwg.org/#concept-node-replace
pub fn ReplaceChild(&self, parent: AbstractNode, node: AbstractNode, child: AbstractNode)
-> Fallible<AbstractNode> {
fail!("stub")
// Step 1.
match parent.type_id() {
DocumentNodeTypeId(..) |
DocumentFragmentNodeTypeId |
ElementNodeTypeId(..) => (),
_ => return Err(HierarchyRequest)
}

// Step 2.
if node.is_inclusive_ancestor_of(parent) {
return Err(HierarchyRequest);
}

// Step 3.
if !parent.is_parent_of(child) {
return Err(NotFound);
}

// Step 4-5.
match node.type_id() {
TextNodeTypeId if parent.is_document() => return Err(HierarchyRequest),
DoctypeNodeTypeId if !parent.is_document() => return Err(HierarchyRequest),
DocumentFragmentNodeTypeId |
DoctypeNodeTypeId |
ElementNodeTypeId(..) |
TextNodeTypeId |
// ProcessingInstructionNodeTypeId |
CommentNodeTypeId => (),
DocumentNodeTypeId(..) => return Err(HierarchyRequest)
}

// Step 6.
match parent.type_id() {
DocumentNodeTypeId(..) => {
match node.type_id() {
// Step 6.1
DocumentFragmentNodeTypeId => {
// Step 6.1.1(b)
if node.children().any(|c| c.is_text()) {
return Err(HierarchyRequest);
}
match node.child_elements().len() {
0 => (),
// Step 6.1.2
1 => {
if parent.child_elements().any(|c| c != child) {
return Err(HierarchyRequest);
}
if AbstractNode::followed_by_doctype(child) {
return Err(HierarchyRequest);
}
},
// Step 6.1.1(a)
_ => return Err(HierarchyRequest)
}
},
// Step 6.2
ElementNodeTypeId(..) => {
if parent.child_elements().any(|c| c != child) {
return Err(HierarchyRequest);
}
if AbstractNode::followed_by_doctype(child) {
return Err(HierarchyRequest);
}
},
// Step 6.3
DoctypeNodeTypeId => {
if parent.children().any(|c| c.is_doctype() && c != child) {
return Err(HierarchyRequest);
}
if parent.children()
.take_while(|&c| c != child)
.any(|c| c.is_element()) {
return Err(HierarchyRequest);
}
},
TextNodeTypeId |
// ProcessingInstructionNodeTypeId |
CommentNodeTypeId => (),
DocumentNodeTypeId(..) => unreachable!()
}
},
_ => ()
}

// Ok if not caught by previous error checks.
if node == child {
return Ok(child);
}

// Step 7-8.
let reference_child = if child.next_sibling() != Some(node) {
child.next_sibling()
} else {
node.next_sibling()
};

// Step 9.
Node::adopt(node, parent.node().owner_doc());

{
let suppress_observers = true;

// Step 10.
Node::remove(child, parent, suppress_observers);

// Step 11.
Node::insert(node, parent, reference_child, suppress_observers);
}

// Step 12-14.
// Step 13: mutation records.
child.node_removed();
if node.type_id() == DocumentFragmentNodeTypeId {
for child_node in node.children() {
child_node.node_inserted();
}
} else {
node.node_inserted();
}

// Step 15.
Ok(child)
}

pub fn RemoveChild(&self, abstract_self: AbstractNode, node: AbstractNode)
Expand Down
2 changes: 0 additions & 2 deletions src/test/html/content/test_document_body.html
Expand Up @@ -11,7 +11,6 @@
is(document.body && document.body.tagName, "BODY", "test1-2, existing document's body");
}

/* TODO: Depends on https://github.com/mozilla/servo/issues/1430
// test2: replace document's body with new body
{
let new_body = document.createElement("body");
Expand All @@ -27,7 +26,6 @@
document.body = new_frameset;
is(new_frameset, document.body, "test2-1, replace document's body with new frameset");
}
*/

// test4: append an invalid element to a new document
{
Expand Down
52 changes: 52 additions & 0 deletions src/test/html/content/test_node_replaceChild.html
@@ -0,0 +1,52 @@
<!doctype html>
<html>
<head>
<script src="harness.js"></script>
</head>
<body>
<script>
// test1: 1-to-1
{
var root = document.createElement("div");
var elem = document.createElement("div");
var foo = document.createTextNode("foo");
var bar = document.createTextNode("bar");

elem.appendChild(bar);
is(elem.replaceChild(bar, bar), bar, "test1-0, 1-to-1");
is(elem.childNodes[0], bar, "test1-1, 1-to-1");

root.appendChild(foo);
is(root.replaceChild(bar, foo), foo, "test1-2, 1-to-1");
is(elem.childNodes.length, 0, "test1-3, 1-to-1");
is(root.childNodes[0], bar, "test1-4, 1-to-1");

elem.appendChild(foo);
is(root.replaceChild(elem, bar), bar, "test1-5, 1-to-1");
is(root.childNodes[0].childNodes[0], foo, "test1-6, 1-to-1");
}

// test2: doctype
{
var doc_doctype = document.doctype;
var new_doctype = document.implementation.createDocumentType("html", null, null);

isnot(doc_doctype, new_doctype, "test2-0, doctype");
is(document.replaceChild(new_doctype, doc_doctype), doc_doctype, "test2-1, doctype");
is(document.doctype, new_doctype, "test2-2, doctype");
}

// test3: documentElement
{
var doc_elem = document.documentElement;
var new_elem = document.createElement("html");

isnot(doc_elem, new_elem, "test3-0, documentElement");
is(document.replaceChild(new_elem, doc_elem), doc_elem, "test3-1, documentElement");
is(document.documentElement, new_elem, "test3-2, documentElement");
}

finish();
</script>
</body>
</html>

0 comments on commit ecaaf4c

Please sign in to comment.