diff options
-rw-r--r-- | src/components/script/dom/bindings/codegen/Bindings.conf | 1 | ||||
-rw-r--r-- | src/components/script/dom/node.rs | 226 | ||||
-rw-r--r-- | src/test/html/content/test_document_body.html | 2 | ||||
-rw-r--r-- | src/test/html/content/test_node_replaceChild.html | 52 |
4 files changed, 224 insertions, 57 deletions
diff --git a/src/components/script/dom/bindings/codegen/Bindings.conf b/src/components/script/dom/bindings/codegen/Bindings.conf index 4f4630861de..5ce384201a1 100644 --- a/src/components/script/dom/bindings/codegen/Bindings.conf +++ b/src/components/script/dom/bindings/codegen/Bindings.conf @@ -312,6 +312,7 @@ DOMInterfaces = { 'pointerType': '', 'needsAbstract': [ 'appendChild', + 'replaceChild', 'nodeName', 'nodeValue', 'removeChild', diff --git a/src/components/script/dom/node.rs b/src/components/script/dom/node.rs index c9f8f06f7b7..1d7acb41906 100644 --- a/src/components/script/dom/node.rs +++ b/src/components/script/dom/node.rs @@ -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 { @@ -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> { @@ -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 => { @@ -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); } }, @@ -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); } }, @@ -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) diff --git a/src/test/html/content/test_document_body.html b/src/test/html/content/test_document_body.html index 880a36370f5..290068d1fcf 100644 --- a/src/test/html/content/test_document_body.html +++ b/src/test/html/content/test_document_body.html @@ -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"); @@ -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 { diff --git a/src/test/html/content/test_node_replaceChild.html b/src/test/html/content/test_node_replaceChild.html new file mode 100644 index 00000000000..a9a4703cc9c --- /dev/null +++ b/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> |