mirror of
https://github.com/servo/servo.git
synced 2025-06-22 08:08:59 +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': '',
|
'pointerType': '',
|
||||||
'needsAbstract': [
|
'needsAbstract': [
|
||||||
'appendChild',
|
'appendChild',
|
||||||
|
'replaceChild',
|
||||||
'nodeName',
|
'nodeName',
|
||||||
'nodeValue',
|
'nodeValue',
|
||||||
'removeChild',
|
'removeChild',
|
||||||
|
|
|
@ -258,6 +258,37 @@ impl AbstractNode {
|
||||||
AbstractNode::from_box(boxed_node)
|
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 {
|
impl<'a> AbstractNode {
|
||||||
|
@ -528,7 +559,7 @@ impl AbstractNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ReplaceChild(self, node: AbstractNode, child: AbstractNode) -> Fallible<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> {
|
pub fn RemoveChild(self, node: AbstractNode) -> Fallible<AbstractNode> {
|
||||||
|
@ -1041,87 +1072,49 @@ impl Node {
|
||||||
// http://dom.spec.whatwg.org/#concept-node-pre-insert
|
// http://dom.spec.whatwg.org/#concept-node-pre-insert
|
||||||
fn pre_insert(node: AbstractNode, parent: AbstractNode, child: Option<AbstractNode>)
|
fn pre_insert(node: AbstractNode, parent: AbstractNode, child: Option<AbstractNode>)
|
||||||
-> Fallible<AbstractNode> {
|
-> Fallible<AbstractNode> {
|
||||||
fn is_inclusive_ancestor_of(node: AbstractNode, parent: AbstractNode) -> bool {
|
|
||||||
node == parent || parent.ancestors().any(|ancestor| ancestor == node)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1.
|
// Step 1.
|
||||||
match parent.type_id() {
|
match parent.type_id() {
|
||||||
DocumentNodeTypeId(..) |
|
DocumentNodeTypeId(..) |
|
||||||
DocumentFragmentNodeTypeId |
|
DocumentFragmentNodeTypeId |
|
||||||
ElementNodeTypeId(..) => (),
|
ElementNodeTypeId(..) => (),
|
||||||
_ => {
|
_ => return Err(HierarchyRequest)
|
||||||
return Err(HierarchyRequest);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2.
|
// Step 2.
|
||||||
if is_inclusive_ancestor_of(node, parent) {
|
if node.is_inclusive_ancestor_of(parent) {
|
||||||
return Err(HierarchyRequest);
|
return Err(HierarchyRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3.
|
// Step 3.
|
||||||
match child {
|
match child {
|
||||||
Some(child) => {
|
Some(child) if !parent.is_parent_of(child) => return Err(NotFound),
|
||||||
if child.parent_node() != Some(parent) {
|
_ => ()
|
||||||
return Err(NotFound);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4.
|
// Step 4-5.
|
||||||
match node.type_id() {
|
|
||||||
DocumentFragmentNodeTypeId |
|
|
||||||
DoctypeNodeTypeId |
|
|
||||||
ElementNodeTypeId(_) |
|
|
||||||
TextNodeTypeId |
|
|
||||||
// ProcessingInstructionNodeTypeId |
|
|
||||||
CommentNodeTypeId => (),
|
|
||||||
DocumentNodeTypeId(..) => return Err(HierarchyRequest),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5.
|
|
||||||
match node.type_id() {
|
match node.type_id() {
|
||||||
TextNodeTypeId => {
|
TextNodeTypeId => {
|
||||||
match node.parent_node() {
|
match node.parent_node() {
|
||||||
Some(parent) if parent.is_document() => return Err(HierarchyRequest),
|
Some(parent) if parent.is_document() => return Err(HierarchyRequest),
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
DoctypeNodeTypeId => {
|
DoctypeNodeTypeId => {
|
||||||
match node.parent_node() {
|
match node.parent_node() {
|
||||||
Some(parent) if !parent.is_document() => return Err(HierarchyRequest),
|
Some(parent) if !parent.is_document() => return Err(HierarchyRequest),
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => (),
|
DocumentFragmentNodeTypeId |
|
||||||
|
ElementNodeTypeId(_) |
|
||||||
|
// ProcessingInstructionNodeTypeId |
|
||||||
|
CommentNodeTypeId => (),
|
||||||
|
DocumentNodeTypeId(..) => return Err(HierarchyRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6.
|
// Step 6.
|
||||||
match parent.type_id() {
|
match parent.type_id() {
|
||||||
DocumentNodeTypeId(_) => {
|
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() {
|
match node.type_id() {
|
||||||
// Step 6.1
|
// Step 6.1
|
||||||
DocumentFragmentNodeTypeId => {
|
DocumentFragmentNodeTypeId => {
|
||||||
|
@ -1138,7 +1131,7 @@ impl Node {
|
||||||
if parent.child_elements().len() > 0 {
|
if parent.child_elements().len() > 0 {
|
||||||
return Err(HierarchyRequest);
|
return Err(HierarchyRequest);
|
||||||
}
|
}
|
||||||
if inclusively_followed_by_doctype(child) {
|
if AbstractNode::inclusively_followed_by_doctype(child) {
|
||||||
return Err(HierarchyRequest);
|
return Err(HierarchyRequest);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1153,7 +1146,7 @@ impl Node {
|
||||||
if parent.child_elements().len() > 0 {
|
if parent.child_elements().len() > 0 {
|
||||||
return Err(HierarchyRequest);
|
return Err(HierarchyRequest);
|
||||||
}
|
}
|
||||||
if inclusively_followed_by_doctype(child) {
|
if AbstractNode::inclusively_followed_by_doctype(child) {
|
||||||
return Err(HierarchyRequest);
|
return Err(HierarchyRequest);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1362,9 +1355,132 @@ impl Node {
|
||||||
Node::pre_insert(node, abstract_self, None)
|
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> {
|
-> 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)
|
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");
|
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
|
// test2: replace document's body with new body
|
||||||
{
|
{
|
||||||
let new_body = document.createElement("body");
|
let new_body = document.createElement("body");
|
||||||
|
@ -27,7 +26,6 @@
|
||||||
document.body = new_frameset;
|
document.body = new_frameset;
|
||||||
is(new_frameset, document.body, "test2-1, replace document's body with 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
|
// 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