mirror of
https://github.com/servo/servo.git
synced 2025-06-21 23:59:00 +01:00
Implement Node::replaceChild()
Implements Node:replaceChild() according to spec below: http://dom.spec.whatwg.org/#concept-node-replace Closes #1430.
This commit is contained in:
parent
9b7425000b
commit
3b82b11054
4 changed files with 224 additions and 57 deletions
|
@ -312,6 +312,7 @@ DOMInterfaces = {
|
|||
'pointerType': '',
|
||||
'needsAbstract': [
|
||||
'appendChild',
|
||||
'replaceChild',
|
||||
'nodeName',
|
||||
'nodeValue',
|
||||
'removeChild',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
52
src/test/html/content/test_node_replaceChild.html
Normal file
52
src/test/html/content/test_node_replaceChild.html
Normal file
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue