diff options
author | Bruno de Oliveira Abinader <bruno.d@partner.samsung.com> | 2014-02-06 14:23:29 -0400 |
---|---|---|
committer | Bruno de Oliveira Abinader <bruno.d@partner.samsung.com> | 2014-03-13 12:34:31 -0400 |
commit | 1703c427bcd6c64961626783f90cd14bf2c97b87 (patch) | |
tree | 72e087ec6fe2c0a8a4c085b1bae3c8e5a3d83f49 /src | |
parent | 5eee401403123a0011c97fba9e2c05dcea56c514 (diff) | |
download | servo-1703c427bcd6c64961626783f90cd14bf2c97b87.tar.gz servo-1703c427bcd6c64961626783f90cd14bf2c97b87.zip |
Implement Node.cloneNode
Spec:
http://dom.spec.whatwg.org/#dom-node-clonenode
Closes #1240.
Diffstat (limited to 'src')
-rw-r--r-- | src/components/script/dom/bindings/codegen/Bindings.conf | 1 | ||||
-rw-r--r-- | src/components/script/dom/node.rs | 141 | ||||
-rw-r--r-- | src/components/script/dom/webidls/Node.webidl | 1 | ||||
-rw-r--r-- | src/test/content/test_node_cloneNode.html | 191 |
4 files changed, 328 insertions, 6 deletions
diff --git a/src/components/script/dom/bindings/codegen/Bindings.conf b/src/components/script/dom/bindings/codegen/Bindings.conf index a002e893823..34deb138af2 100644 --- a/src/components/script/dom/bindings/codegen/Bindings.conf +++ b/src/components/script/dom/bindings/codegen/Bindings.conf @@ -82,6 +82,7 @@ DOMInterfaces = { 'needsAbstract': [ 'appendChild', 'childNodes', + 'cloneNode', 'compareDocumentPosition', 'contains', 'insertBefore', diff --git a/src/components/script/dom/node.rs b/src/components/script/dom/node.rs index 13d941308a7..000a19be847 100644 --- a/src/components/script/dom/node.rs +++ b/src/components/script/dom/node.rs @@ -4,7 +4,9 @@ //! 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::ProcessingInstructionCast; 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::utils; 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::element::{Element, ElementTypeId, HTMLAnchorElementTypeId, IElement}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; @@ -20,6 +24,7 @@ use dom::nodelist::{NodeList}; use dom::text::Text; use dom::processinginstruction::ProcessingInstruction; use dom::window::Window; +use html::hubbub_html_parser::build_element_from_tag; use layout_interface::{LayoutChan, ReapLayoutDataMsg, UntrustedNodeAddress}; use layout_interface::TrustedNodeAddress; 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 { pub fn ancestors(&self) -> 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(©); + } + 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(©); + 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(©); + 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 pub fn InsertBefore(&self, abstract_self: &mut JS<Node>, node: &mut JS<Node>, child: Option<JS<Node>>) -> Fallible<JS<Node>> { @@ -1428,9 +1557,11 @@ impl Node { } // http://dom.spec.whatwg.org/#dom-node-clonenode - pub fn CloneNode(&self, _deep: bool) -> Fallible<JS<Node>> { - // FIXME: stub - https://github.com/mozilla/servo/issues/1240 - fail!("stub") + pub fn CloneNode(&self, abstract_self: &mut JS<Node>, deep: bool) -> JS<Node> { + match deep { + true => Node::clone(abstract_self, None, CloneChildren), + false => Node::clone(abstract_self, None, DoNotCloneChildren) + } } // http://dom.spec.whatwg.org/#dom-node-isequalnode diff --git a/src/components/script/dom/webidls/Node.webidl b/src/components/script/dom/webidls/Node.webidl index 0670e6eb3f6..2a13d6ab381 100644 --- a/src/components/script/dom/webidls/Node.webidl +++ b/src/components/script/dom/webidls/Node.webidl @@ -52,7 +52,6 @@ interface Node : EventTarget { attribute DOMString? textContent; void normalize(); - [Throws] Node cloneNode(optional boolean deep = true); boolean isEqualNode(Node? node); diff --git a/src/test/content/test_node_cloneNode.html b/src/test/content/test_node_cloneNode.html new file mode 100644 index 00000000000..b6690b761ce --- /dev/null +++ b/src/test/content/test_node_cloneNode.html @@ -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> |