Implement Node.cloneNode

Spec:
http://dom.spec.whatwg.org/#dom-node-clonenode

Closes #1240.
This commit is contained in:
Bruno de Oliveira Abinader 2014-02-06 14:23:29 -04:00
parent 5eee401403
commit 1703c427bc
4 changed files with 328 additions and 6 deletions

View file

@ -82,6 +82,7 @@ DOMInterfaces = {
'needsAbstract': [ 'needsAbstract': [
'appendChild', 'appendChild',
'childNodes', 'childNodes',
'cloneNode',
'compareDocumentPosition', 'compareDocumentPosition',
'contains', 'contains',
'insertBefore', 'insertBefore',

View file

@ -4,7 +4,9 @@
//! The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements. //! The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements.
use dom::bindings::codegen::InheritTypes::{DocumentTypeCast, ElementCast, TextCast, NodeCast}; use dom::attr::Attr;
use dom::bindings::codegen::InheritTypes::{CommentCast, DocumentCast, DocumentTypeCast};
use dom::bindings::codegen::InheritTypes::{ElementCast, TextCast, NodeCast};
use dom::bindings::codegen::InheritTypes::{CharacterDataCast, NodeBase, NodeDerived}; use dom::bindings::codegen::InheritTypes::{CharacterDataCast, NodeBase, NodeDerived};
use dom::bindings::codegen::InheritTypes::ProcessingInstructionCast; use dom::bindings::codegen::InheritTypes::ProcessingInstructionCast;
use dom::bindings::js::JS; use dom::bindings::js::JS;
@ -12,7 +14,9 @@ use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
use dom::bindings::error::{ErrorResult, Fallible, NotFound, HierarchyRequest}; use dom::bindings::error::{ErrorResult, Fallible, NotFound, HierarchyRequest};
use dom::bindings::utils; use dom::bindings::utils;
use dom::characterdata::CharacterData; use dom::characterdata::CharacterData;
use dom::document::Document; use dom::comment::Comment;
use dom::document::{Document, HTMLDocument, NonHTMLDocument};
use dom::documentfragment::DocumentFragment;
use dom::documenttype::DocumentType; use dom::documenttype::DocumentType;
use dom::element::{Element, ElementTypeId, HTMLAnchorElementTypeId, IElement}; use dom::element::{Element, ElementTypeId, HTMLAnchorElementTypeId, IElement};
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
@ -20,6 +24,7 @@ use dom::nodelist::{NodeList};
use dom::text::Text; use dom::text::Text;
use dom::processinginstruction::ProcessingInstruction; use dom::processinginstruction::ProcessingInstruction;
use dom::window::Window; use dom::window::Window;
use html::hubbub_html_parser::build_element_from_tag;
use layout_interface::{LayoutChan, ReapLayoutDataMsg, UntrustedNodeAddress}; use layout_interface::{LayoutChan, ReapLayoutDataMsg, UntrustedNodeAddress};
use layout_interface::TrustedNodeAddress; use layout_interface::TrustedNodeAddress;
use servo_util::str::{DOMString, null_str_as_empty}; use servo_util::str::{DOMString, null_str_as_empty};
@ -722,6 +727,12 @@ fn gather_abstract_nodes(cur: &JS<Node>, refs: &mut ~[JS<Node>], postorder: bool
} }
} }
/// Specifies whether children must be recursively cloned or not.
enum CloneChildrenFlag {
CloneChildren,
DoNotCloneChildren
}
impl Node { impl Node {
pub fn ancestors(&self) -> AncestorIterator { pub fn ancestors(&self) -> AncestorIterator {
AncestorIterator { AncestorIterator {
@ -1271,6 +1282,124 @@ impl Node {
} }
} }
// http://dom.spec.whatwg.org/#concept-node-clone
fn clone(node: &JS<Node>, maybe_doc: Option<&JS<Document>>, clone_children: CloneChildrenFlag)
-> JS<Node> {
fn clone_recursively(node: &JS<Node>, copy: &mut JS<Node>, doc: &JS<Document>) {
for ref child in node.get().children() {
let mut cloned = Node::clone(child, Some(doc), DoNotCloneChildren);
match Node::pre_insert(&mut cloned, copy, None) {
Ok(ref mut appended) => clone_recursively(child, appended, doc),
Err(..) => fail!("an error occurred while appending children")
}
}
}
// Step 1.
let mut document = match maybe_doc {
Some(doc) => doc.clone(),
None => node.get().owner_doc().clone()
};
// Step 2.
// XXXabinader: clone() for each node as trait?
let mut copy: JS<Node> = match node.type_id() {
DoctypeNodeTypeId => {
let doctype: JS<DocumentType> = DocumentTypeCast::to(node);
let doctype = doctype.get();
let doctype = DocumentType::new(doctype.name.clone(),
Some(doctype.public_id.clone()),
Some(doctype.system_id.clone()), &document);
NodeCast::from(&doctype)
},
DocumentFragmentNodeTypeId => {
let doc_fragment = DocumentFragment::new(&document);
NodeCast::from(&doc_fragment)
},
CommentNodeTypeId => {
let comment: JS<Comment> = CommentCast::to(node);
let comment = comment.get();
let comment = Comment::new(comment.characterdata.data.clone(), &document);
NodeCast::from(&comment)
},
DocumentNodeTypeId => {
let document: JS<Document> = DocumentCast::to(node);
let document = document.get();
let is_html_doc = match document.is_html_document {
true => HTMLDocument,
false => NonHTMLDocument
};
let document = Document::new(&document.window, Some(document.url().clone()),
is_html_doc, None);
NodeCast::from(&document)
},
ElementNodeTypeId(..) => {
let element: JS<Element> = ElementCast::to(node);
let element = element.get();
let element = build_element_from_tag(element.tag_name.clone(), &document);
NodeCast::from(&element)
},
TextNodeTypeId => {
let text: JS<Text> = TextCast::to(node);
let text = text.get();
let text = Text::new(text.characterdata.data.clone(), &document);
NodeCast::from(&text)
},
ProcessingInstructionNodeTypeId => {
let pi: JS<ProcessingInstruction> = ProcessingInstructionCast::to(node);
let pi = pi.get();
let pi = ProcessingInstruction::new(pi.target.clone(),
pi.characterdata.data.clone(), &document);
NodeCast::from(&pi)
},
};
// Step 3.
if copy.is_document() {
document = DocumentCast::to(&copy);
}
assert_eq!(copy.get().owner_doc(), &document);
// Step 4 (some data already copied in step 2).
match node.type_id() {
DocumentNodeTypeId => {
let node_doc: JS<Document> = DocumentCast::to(node);
let node_doc = node_doc.get();
let mut copy_doc: JS<Document> = DocumentCast::to(&copy);
let copy_doc = copy_doc.get_mut();
copy_doc.set_encoding_name(node_doc.encoding_name.clone());
copy_doc.set_quirks_mode(node_doc.quirks_mode());
},
ElementNodeTypeId(..) => {
let node_elem: JS<Element> = ElementCast::to(node);
let node_elem = node_elem.get();
let mut copy_elem: JS<Element> = ElementCast::to(&copy);
let copy_elem = copy_elem.get_mut();
// FIXME: https://github.com/mozilla/servo/issues/1737
copy_elem.namespace = node_elem.namespace.clone();
for attr in node_elem.attrs.iter() {
let attr = attr.get();
copy_elem.attrs.push(Attr::new_ns(&document.get().window,
attr.local_name.clone(), attr.value.clone(),
attr.name.clone(), attr.namespace.clone(),
attr.prefix.clone()));
}
},
_ => ()
}
// Step 5: cloning steps.
// Step 6.
match clone_children {
CloneChildren => clone_recursively(node, &mut copy, &document),
DoNotCloneChildren => ()
}
// Step 7.
copy
}
// http://dom.spec.whatwg.org/#dom-node-insertbefore // http://dom.spec.whatwg.org/#dom-node-insertbefore
pub fn InsertBefore(&self, abstract_self: &mut JS<Node>, node: &mut JS<Node>, child: Option<JS<Node>>) pub fn InsertBefore(&self, abstract_self: &mut JS<Node>, node: &mut JS<Node>, child: Option<JS<Node>>)
-> Fallible<JS<Node>> { -> Fallible<JS<Node>> {
@ -1428,9 +1557,11 @@ impl Node {
} }
// http://dom.spec.whatwg.org/#dom-node-clonenode // http://dom.spec.whatwg.org/#dom-node-clonenode
pub fn CloneNode(&self, _deep: bool) -> Fallible<JS<Node>> { pub fn CloneNode(&self, abstract_self: &mut JS<Node>, deep: bool) -> JS<Node> {
// FIXME: stub - https://github.com/mozilla/servo/issues/1240 match deep {
fail!("stub") true => Node::clone(abstract_self, None, CloneChildren),
false => Node::clone(abstract_self, None, DoNotCloneChildren)
}
} }
// http://dom.spec.whatwg.org/#dom-node-isequalnode // http://dom.spec.whatwg.org/#dom-node-isequalnode

View file

@ -52,7 +52,6 @@ interface Node : EventTarget {
attribute DOMString? textContent; attribute DOMString? textContent;
void normalize(); void normalize();
[Throws]
Node cloneNode(optional boolean deep = true); Node cloneNode(optional boolean deep = true);
boolean isEqualNode(Node? node); boolean isEqualNode(Node? node);

View file

@ -0,0 +1,191 @@
<!DOCTYPE html>
<html>
<head>
<script src="harness.js"></script>
<script>
function check_copy(orig, copy, type) {
is_not(orig, copy);
is_a(orig, type);
is_a(copy, type);
}
function create_element_and_check(localName, type) {
var element = document.createElement(localName);
var copy = element.cloneNode();
check_copy(element, copy, type);
}
// test1: createElement
{
create_element_and_check("a", HTMLAnchorElement);
create_element_and_check("applet", HTMLAppletElement);
create_element_and_check("area", HTMLAreaElement);
create_element_and_check("aside", HTMLElement);
create_element_and_check("audio", HTMLAudioElement);
create_element_and_check("b", HTMLElement);
create_element_and_check("base", HTMLBaseElement);
create_element_and_check("body", HTMLBodyElement);
create_element_and_check("br", HTMLBRElement);
create_element_and_check("button", HTMLButtonElement);
create_element_and_check("canvas", HTMLCanvasElement);
create_element_and_check("caption", HTMLTableCaptionElement);
create_element_and_check("col", HTMLTableColElement);
create_element_and_check("colgroup", HTMLTableColElement);
create_element_and_check("data", HTMLDataElement);
create_element_and_check("datalist", HTMLDataListElement);
create_element_and_check("del", HTMLModElement);
create_element_and_check("dir", HTMLDirectoryElement);
create_element_and_check("div", HTMLDivElement);
create_element_and_check("dl", HTMLDListElement);
create_element_and_check("embed", HTMLEmbedElement);
create_element_and_check("fieldset", HTMLFieldSetElement);
create_element_and_check("font", HTMLFontElement);
create_element_and_check("form", HTMLFormElement);
create_element_and_check("frame", HTMLFrameElement);
create_element_and_check("frameset", HTMLFrameSetElement);
create_element_and_check("h1", HTMLHeadingElement);
create_element_and_check("h2", HTMLHeadingElement);
create_element_and_check("h3", HTMLHeadingElement);
create_element_and_check("h4", HTMLHeadingElement);
create_element_and_check("h5", HTMLHeadingElement);
create_element_and_check("h6", HTMLHeadingElement);
create_element_and_check("head", HTMLHeadElement);
create_element_and_check("hr", HTMLHRElement);
create_element_and_check("html", HTMLHtmlElement);
create_element_and_check("i", HTMLElement);
create_element_and_check("iframe", HTMLIFrameElement);
create_element_and_check("img", HTMLImageElement);
create_element_and_check("input", HTMLInputElement);
create_element_and_check("ins", HTMLModElement);
create_element_and_check("label", HTMLLabelElement);
create_element_and_check("legend", HTMLLegendElement);
create_element_and_check("li", HTMLLIElement);
create_element_and_check("link", HTMLLinkElement);
create_element_and_check("main", HTMLMainElement);
create_element_and_check("map", HTMLMapElement);
create_element_and_check("meta", HTMLMetaElement);
create_element_and_check("meter", HTMLMeterElement);
create_element_and_check("object", HTMLObjectElement);
create_element_and_check("ol", HTMLOListElement);
create_element_and_check("optgroup", HTMLOptGroupElement);
create_element_and_check("option", HTMLOptionElement);
create_element_and_check("output", HTMLOutputElement);
create_element_and_check("p", HTMLParagraphElement);
create_element_and_check("param", HTMLParamElement);
create_element_and_check("pre", HTMLPreElement);
create_element_and_check("progress", HTMLProgressElement);
create_element_and_check("q", HTMLQuoteElement);
create_element_and_check("script", HTMLScriptElement);
create_element_and_check("section", HTMLElement);
create_element_and_check("select", HTMLSelectElement);
create_element_and_check("small", HTMLElement);
create_element_and_check("source", HTMLSourceElement);
create_element_and_check("span", HTMLSpanElement);
create_element_and_check("style", HTMLStyleElement);
create_element_and_check("table", HTMLTableElement);
create_element_and_check("tbody", HTMLTableSectionElement);
create_element_and_check("td", HTMLTableDataCellElement);
create_element_and_check("template", HTMLTemplateElement);
create_element_and_check("textarea", HTMLTextAreaElement);
create_element_and_check("th", HTMLTableHeaderCellElement);
create_element_and_check("time", HTMLTimeElement);
create_element_and_check("title", HTMLTitleElement);
create_element_and_check("tr", HTMLTableRowElement);
create_element_and_check("track", HTMLTrackElement);
create_element_and_check("ul", HTMLUListElement);
create_element_and_check("video", HTMLVideoElement);
create_element_and_check("unknown", HTMLUnknownElement);
}
// test2: createDocumentFragment
{
var fragment = document.createDocumentFragment();
var copy = fragment.cloneNode();
check_copy(fragment, copy, DocumentFragment);
}
// test3: createTextNode
{
var text = document.createTextNode("hello world");
var copy = text.cloneNode();
check_copy(text, copy, Text);
is(text.data, copy.data);
is(text.wholeText, copy.wholeText);
}
// test4: createComment
{
var comment = document.createComment("a comment");
var copy = comment.cloneNode();
check_copy(comment, copy, Comment);
is(comment.data, copy.data);
}
// test5: createProcessingInstruction
{
var pi = document.createProcessingInstruction("target", "data");
var copy = pi.cloneNode();
check_copy(pi, copy, ProcessingInstruction);
is(pi.data, copy.data);
is(pi.target, pi.target);
}
// test6: implementation.createDocumentType
{
var doctype = document.implementation.createDocumentType("html", "public", "system");
var copy = doctype.cloneNode();
check_copy(doctype, copy, DocumentType);
is(doctype.name, copy.name);
is(doctype.publicId, copy.publicId);
is(doctype.systemId, copy.systemId);
}
// test7: implementation.createDocument
{
// FIXME: https://github.com/mozilla/servo/issues/1509
}
// test8: implementation.createHTMLDocument
{
var html = document.implementation.createHTMLDocument("title");
var copy = html.cloneNode();
check_copy(html, copy, Document);
// FIXME: https://github.com/mozilla/servo/issues/1640
//is(html.title, copy.title);
}
// test9: node with children
{
var parent = document.createElement("div");
var child1 = document.createElement("div");
var child2 = document.createElement("div");
var grandChild = document.createElement("div");
child2.appendChild(grandChild);
parent.appendChild(child1);
parent.appendChild(child2);
var deep = true;
var copy = parent.cloneNode(deep);
check_copy(parent, copy, HTMLDivElement);
is(copy.childNodes.length, 2);
check_copy(child1, copy.childNodes[0], HTMLDivElement);
is(copy.childNodes[0].childNodes.length, 0);
check_copy(child2, copy.childNodes[1], HTMLDivElement);
is(copy.childNodes[1].childNodes.length, 1);
check_copy(grandChild, copy.childNodes[1].childNodes[0], HTMLDivElement);
deep = false;
copy = parent.cloneNode(deep);
check_copy(parent, copy, HTMLDivElement);
is(copy.childNodes.length, 0);
}
finish();
</script>
</head>
</html>