/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements. use dom::attr::{Attr, AttrHelpers}; use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods; use dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods}; use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; use dom::bindings::codegen::InheritTypes::{CommentCast, DocumentCast, DocumentTypeCast}; use dom::bindings::codegen::InheritTypes::{ElementCast, TextCast, NodeCast, ElementDerived}; use dom::bindings::codegen::InheritTypes::{CharacterDataCast, NodeBase, NodeDerived}; use dom::bindings::codegen::InheritTypes::{ProcessingInstructionCast, EventTargetCast}; use dom::bindings::codegen::InheritTypes::{HTMLLegendElementDerived, HTMLFieldSetElementDerived}; use dom::bindings::codegen::InheritTypes::HTMLOptGroupElementDerived; use dom::bindings::error::{Fallible, NotFound, HierarchyRequest, Syntax}; use dom::bindings::global::GlobalRef; use dom::bindings::global; use dom::bindings::js::{JS, JSRef, RootedReference, Temporary, Root}; use dom::bindings::js::{OptionalSettable, TemporaryPushable, OptionalRootedRootable}; use dom::bindings::js::{ResultRootable, OptionalRootable, MutNullableJS}; use dom::bindings::trace::JSTraceable; use dom::bindings::utils; use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object}; use dom::characterdata::CharacterData; use dom::comment::Comment; use dom::document::{Document, DocumentHelpers, HTMLDocument, NonHTMLDocument}; use dom::documentfragment::DocumentFragment; use dom::documenttype::DocumentType; use dom::element::{AttributeHandlers, Element, ElementTypeId}; use dom::element::{HTMLAnchorElementTypeId, HTMLButtonElementTypeId, ElementHelpers}; use dom::element::{HTMLInputElementTypeId, HTMLSelectElementTypeId}; use dom::element::{HTMLTextAreaElementTypeId, HTMLOptGroupElementTypeId}; use dom::element::{HTMLOptionElementTypeId, HTMLFieldSetElementTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::nodelist::NodeList; use dom::processinginstruction::ProcessingInstruction; use dom::text::Text; use dom::virtualmethods::{VirtualMethods, vtable_for}; use dom::window::Window; use geom::rect::Rect; use html::hubbub_html_parser::build_element_from_tag; use layout_interface::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC, LayoutChan, ReapLayoutDataMsg, TrustedNodeAddress, UntrustedNodeAddress}; use devtools_traits::NodeInfo; use servo_util::geometry::Au; use servo_util::str::{DOMString, null_str_as_empty}; use style::{parse_selector_list_from_str, matches}; use js::jsapi::{JSContext, JSObject, JSTracer, JSRuntime}; use js::jsfriendapi; use libc; use libc::uintptr_t; use std::cell::{RefCell, Ref, RefMut}; use std::default::Default; use std::iter::{Map, Filter}; use std::mem; use style; use style::ComputedValues; use sync::Arc; use uuid; // // The basic Node structure // /// An HTML node. #[jstraceable] #[must_root] pub struct Node { /// The JavaScript reflector for this node. pub eventtarget: EventTarget, /// The type of node that this is. type_id: NodeTypeId, /// The parent of this node. parent_node: MutNullableJS, /// The first child of this node. first_child: MutNullableJS, /// The last child of this node. last_child: MutNullableJS, /// The next sibling of this node. next_sibling: MutNullableJS, /// The previous sibling of this node. prev_sibling: MutNullableJS, /// The document that this node belongs to. owner_doc: MutNullableJS, /// The live list of children return by .childNodes. child_list: MutNullableJS, /// A bitfield of flags for node items. flags: RefCell, /// Layout information. Only the layout task may touch this data. /// /// Must be sent back to the layout task to be destroyed when this /// node is finalized. pub layout_data: LayoutDataRef, unique_id: RefCell, } impl NodeDerived for EventTarget { fn is_node(&self) -> bool { match self.type_id { NodeTargetTypeId(_) => true, _ => false } } } bitflags! { #[doc = "Flags for node items."] #[jstraceable] flags NodeFlags: u8 { #[doc = "Specifies whether this node is in a document."] static IsInDoc = 0x01, #[doc = "Specifies whether this node is in hover state."] static InHoverState = 0x02, #[doc = "Specifies whether this node is in disabled state."] static InDisabledState = 0x04, #[doc = "Specifies whether this node is in enabled state."] static InEnabledState = 0x08 } } impl NodeFlags { pub fn new(type_id: NodeTypeId) -> NodeFlags { match type_id { DocumentNodeTypeId => IsInDoc, // The following elements are enabled by default. ElementNodeTypeId(HTMLButtonElementTypeId) | ElementNodeTypeId(HTMLInputElementTypeId) | ElementNodeTypeId(HTMLSelectElementTypeId) | ElementNodeTypeId(HTMLTextAreaElementTypeId) | ElementNodeTypeId(HTMLOptGroupElementTypeId) | ElementNodeTypeId(HTMLOptionElementTypeId) | //ElementNodeTypeId(HTMLMenuItemElementTypeId) | ElementNodeTypeId(HTMLFieldSetElementTypeId) => InEnabledState, _ => NodeFlags::empty(), } } } #[unsafe_destructor] impl Drop for Node { fn drop(&mut self) { unsafe { self.reap_layout_data(); } } } /// suppress observers flag /// http://dom.spec.whatwg.org/#concept-node-insert /// http://dom.spec.whatwg.org/#concept-node-remove enum SuppressObserver { Suppressed, Unsuppressed } /// Layout data that is shared between the script and layout tasks. pub struct SharedLayoutData { /// The results of CSS styling for this node. pub style: Option>, } /// Encapsulates the abstract layout data. pub struct LayoutData { chan: Option, _shared_data: SharedLayoutData, _data: *const (), } pub struct LayoutDataRef { pub data_cell: RefCell>, } untraceable!(LayoutDataRef) impl LayoutDataRef { pub fn new() -> LayoutDataRef { LayoutDataRef { data_cell: RefCell::new(None), } } /// Returns true if there is layout data present. #[inline] pub fn is_present(&self) -> bool { self.data_cell.borrow().is_some() } /// Take the chan out of the layout data if it is present. pub fn take_chan(&self) -> Option { let mut layout_data = self.data_cell.borrow_mut(); match &mut *layout_data { &None => None, &Some(ref mut layout_data) => Some(layout_data.chan.take().unwrap()), } } /// Borrows the layout data immutably, *asserting that there are no mutators*. Bad things will /// happen if you try to mutate the layout data while this is held. This is the only thread- /// safe layout data accessor. #[inline] pub unsafe fn borrow_unchecked(&self) -> *const Option { mem::transmute(&self.data_cell) } /// Borrows the layout data immutably. This function is *not* thread-safe. #[inline] pub fn borrow<'a>(&'a self) -> Ref<'a,Option> { self.data_cell.borrow() } /// Borrows the layout data mutably. This function is *not* thread-safe. /// /// FIXME(pcwalton): We should really put this behind a `MutLayoutView` phantom type, to /// prevent CSS selector matching from mutably accessing nodes it's not supposed to and racing /// on it. This has already resulted in one bug! #[inline] pub fn borrow_mut<'a>(&'a self) -> RefMut<'a,Option> { self.data_cell.borrow_mut() } } /// The different types of nodes. #[deriving(PartialEq)] #[jstraceable] pub enum NodeTypeId { DoctypeNodeTypeId, DocumentFragmentNodeTypeId, CommentNodeTypeId, DocumentNodeTypeId, ElementNodeTypeId(ElementTypeId), TextNodeTypeId, ProcessingInstructionNodeTypeId, } trait PrivateNodeHelpers { fn node_inserted(self); fn node_removed(self, parent_in_doc: bool); fn add_child(self, new_child: JSRef, before: Option>); fn remove_child(self, child: JSRef); } impl<'a> PrivateNodeHelpers for JSRef<'a, Node> { // http://dom.spec.whatwg.org/#node-is-inserted fn node_inserted(self) { assert!(self.parent_node().is_some()); let document = document_from_node(self).root(); let is_in_doc = self.is_in_doc(); for node in self.traverse_preorder() { vtable_for(&node).bind_to_tree(is_in_doc); } let parent = self.parent_node().root(); parent.map(|parent| vtable_for(&*parent).child_inserted(self)); document.content_changed(); } // http://dom.spec.whatwg.org/#node-is-removed fn node_removed(self, parent_in_doc: bool) { assert!(self.parent_node().is_none()); let document = document_from_node(self).root(); for node in self.traverse_preorder() { vtable_for(&node).unbind_from_tree(parent_in_doc); } document.content_changed(); } // // Pointer stitching // /// Adds a new child to the end of this node's list of children. /// /// Fails unless `new_child` is disconnected from the tree. fn add_child(self, new_child: JSRef, before: Option>) { let doc = self.owner_doc().root(); doc.wait_until_safe_to_modify_dom(); assert!(new_child.parent_node().is_none()); assert!(new_child.prev_sibling().is_none()); assert!(new_child.next_sibling().is_none()); match before { Some(ref before) => { assert!(before.parent_node().root().root_ref() == Some(self)); match before.prev_sibling().root() { None => { assert!(Some(*before) == self.first_child().root().root_ref()); self.first_child.assign(Some(new_child)); }, Some(prev_sibling) => { prev_sibling.next_sibling.assign(Some(new_child)); new_child.prev_sibling.assign(Some(*prev_sibling)); }, } before.prev_sibling.assign(Some(new_child)); new_child.next_sibling.assign(Some(*before)); }, None => { match self.last_child().root() { None => self.first_child.assign(Some(new_child)), Some(last_child) => { assert!(last_child.next_sibling().is_none()); last_child.next_sibling.assign(Some(new_child)); new_child.prev_sibling.assign(Some(*last_child)); } } self.last_child.assign(Some(new_child)); }, } new_child.parent_node.assign(Some(self)); } /// Removes the given child from this node's list of children. /// /// Fails unless `child` is a child of this node. fn remove_child(self, child: JSRef) { let doc = self.owner_doc().root(); doc.wait_until_safe_to_modify_dom(); assert!(child.parent_node().root().root_ref() == Some(self)); match child.prev_sibling.get().root() { None => { self.first_child.assign(child.next_sibling.get()); } Some(prev_sibling) => { prev_sibling.next_sibling.assign(child.next_sibling.get()); } } match child.next_sibling.get().root() { None => { self.last_child.assign(child.prev_sibling.get()); } Some(next_sibling) => { next_sibling.prev_sibling.assign(child.prev_sibling.get()); } } child.prev_sibling.clear(); child.next_sibling.clear(); child.parent_node.clear(); } } pub trait NodeHelpers<'a> { fn ancestors(self) -> AncestorIterator<'a>; fn children(self) -> AbstractNodeChildrenIterator<'a>; fn child_elements(self) -> ChildElementIterator<'a>; fn following_siblings(self) -> AbstractNodeChildrenIterator<'a>; fn is_in_doc(self) -> bool; fn is_inclusive_ancestor_of(self, parent: JSRef) -> bool; fn is_parent_of(self, child: JSRef) -> bool; fn type_id(self) -> NodeTypeId; fn parent_node(self) -> Option>; fn first_child(self) -> Option>; fn last_child(self) -> Option>; fn prev_sibling(self) -> Option>; fn next_sibling(self) -> Option>; fn owner_doc(self) -> Temporary; fn set_owner_doc(self, document: JSRef); fn is_in_html_doc(self) -> bool; fn wait_until_safe_to_modify_dom(self); fn is_element(self) -> bool; fn is_document(self) -> bool; fn is_doctype(self) -> bool; fn is_text(self) -> bool; fn is_anchor_element(self) -> bool; fn get_hover_state(self) -> bool; fn set_hover_state(self, state: bool); fn get_disabled_state(self) -> bool; fn set_disabled_state(self, state: bool); fn get_enabled_state(self) -> bool; fn set_enabled_state(self, state: bool); fn dump(self); fn dump_indent(self, indent: uint); fn debug_str(self) -> String; fn traverse_preorder(self) -> TreeIterator<'a>; fn sequential_traverse_postorder(self) -> TreeIterator<'a>; fn inclusively_following_siblings(self) -> AbstractNodeChildrenIterator<'a>; fn to_trusted_node_address(self) -> TrustedNodeAddress; fn get_bounding_content_box(self) -> Rect; fn get_content_boxes(self) -> Vec>; fn query_selector(self, selectors: DOMString) -> Fallible>>; fn query_selector_all(self, selectors: DOMString) -> Fallible>; fn remove_self(self); fn get_unique_id(self) -> String; fn summarize(self) -> NodeInfo; } impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { /// Dumps the subtree rooted at this node, for debugging. fn dump(self) { self.dump_indent(0); } /// Dumps the node tree, for debugging, with indentation. fn dump_indent(self, indent: uint) { let mut s = String::new(); for _ in range(0, indent) { s.push_str(" "); } s.push_str(self.debug_str().as_slice()); debug!("{:s}", s); // FIXME: this should have a pure version? for kid in self.children() { kid.dump_indent(indent + 1u) } } /// Returns a string that describes this node. fn debug_str(self) -> String { format!("{:?}", self.type_id) } fn is_in_doc(self) -> bool { self.flags.borrow().contains(IsInDoc) } /// Returns the type ID of this node. Fails if this node is borrowed mutably. fn type_id(self) -> NodeTypeId { self.type_id } fn parent_node(self) -> Option> { self.parent_node.get() } fn first_child(self) -> Option> { self.first_child.get() } fn last_child(self) -> Option> { self.last_child.get() } /// Returns the previous sibling of this node. Fails if this node is borrowed mutably. fn prev_sibling(self) -> Option> { self.prev_sibling.get() } /// Returns the next sibling of this node. Fails if this node is borrowed mutably. fn next_sibling(self) -> Option> { self.next_sibling.get() } #[inline] fn is_element(self) -> bool { match self.type_id { ElementNodeTypeId(..) => true, _ => false } } #[inline] fn is_document(self) -> bool { self.type_id == DocumentNodeTypeId } #[inline] fn is_anchor_element(self) -> bool { self.type_id == ElementNodeTypeId(HTMLAnchorElementTypeId) } #[inline] fn is_doctype(self) -> bool { self.type_id == DoctypeNodeTypeId } #[inline] fn is_text(self) -> bool { self.type_id == TextNodeTypeId } fn get_hover_state(self) -> bool { self.flags.borrow().contains(InHoverState) } fn set_hover_state(self, state: bool) { if state { self.flags.borrow_mut().insert(InHoverState); } else { self.flags.borrow_mut().remove(InHoverState); } } fn get_disabled_state(self) -> bool { self.flags.borrow().contains(InDisabledState) } fn set_disabled_state(self, state: bool) { if state { self.flags.borrow_mut().insert(InDisabledState); } else { self.flags.borrow_mut().remove(InDisabledState); } } fn get_enabled_state(self) -> bool { self.flags.borrow().contains(InEnabledState) } fn set_enabled_state(self, state: bool) { if state { self.flags.borrow_mut().insert(InEnabledState); } else { self.flags.borrow_mut().remove(InEnabledState); } } /// Iterates over this node and all its descendants, in preorder. fn traverse_preorder(self) -> TreeIterator<'a> { let mut nodes = vec!(); gather_abstract_nodes(self, &mut nodes, false); TreeIterator::new(nodes) } /// Iterates over this node and all its descendants, in postorder. fn sequential_traverse_postorder(self) -> TreeIterator<'a> { let mut nodes = vec!(); gather_abstract_nodes(self, &mut nodes, true); TreeIterator::new(nodes) } fn inclusively_following_siblings(self) -> AbstractNodeChildrenIterator<'a> { AbstractNodeChildrenIterator { current_node: Some(self.clone()), } } fn is_inclusive_ancestor_of(self, parent: JSRef) -> bool { self == parent || parent.ancestors().any(|ancestor| ancestor == self) } fn following_siblings(self) -> AbstractNodeChildrenIterator<'a> { AbstractNodeChildrenIterator { current_node: self.next_sibling().root().map(|next| next.clone()), } } fn is_parent_of(self, child: JSRef) -> bool { match child.parent_node() { Some(ref parent) if parent == &Temporary::from_rooted(self) => true, _ => false } } fn to_trusted_node_address(self) -> TrustedNodeAddress { TrustedNodeAddress(self.deref() as *const Node as *const libc::c_void) } fn get_bounding_content_box(self) -> Rect { let window = window_from_node(self).root(); let page = window.page(); let addr = self.to_trusted_node_address(); let ContentBoxResponse(rect) = page.layout().content_box(addr); rect } fn get_content_boxes(self) -> Vec> { let window = window_from_node(self).root(); let page = window.page(); let addr = self.to_trusted_node_address(); let ContentBoxesResponse(rects) = page.layout().content_boxes(addr); rects } // http://dom.spec.whatwg.org/#dom-parentnode-queryselector fn query_selector(self, selectors: DOMString) -> Fallible>> { // Step 1. match parse_selector_list_from_str(selectors.as_slice()) { // Step 2. Err(()) => return Err(Syntax), // Step 3. Ok(ref selectors) => { let root = self.ancestors().last().unwrap_or(self.clone()); for node in root.traverse_preorder() { if node.is_element() && matches(selectors, &node, &mut None) { let elem: JSRef = ElementCast::to_ref(node).unwrap(); return Ok(Some(Temporary::from_rooted(elem))); } } } } Ok(None) } // http://dom.spec.whatwg.org/#dom-parentnode-queryselectorall fn query_selector_all(self, selectors: DOMString) -> Fallible> { // Step 1. let nodes; let root = self.ancestors().last().unwrap_or(self.clone()); match parse_selector_list_from_str(selectors.as_slice()) { // Step 2. Err(()) => return Err(Syntax), // Step 3. Ok(ref selectors) => { nodes = root.traverse_preorder().filter( // TODO(cgaebel): Is it worth it to build a bloom filter here // (instead of passing `None`)? Probably. |node| node.is_element() && matches(selectors, node, &mut None)).collect() } } let window = window_from_node(self).root(); Ok(NodeList::new_simple_list(*window, nodes)) } fn ancestors(self) -> AncestorIterator<'a> { AncestorIterator { current: self.parent_node.get().map(|node| (*node.root()).clone()), } } fn owner_doc(self) -> Temporary { self.owner_doc.get().unwrap() } fn set_owner_doc(self, document: JSRef) { self.owner_doc.assign(Some(document.clone())); } fn is_in_html_doc(self) -> bool { self.owner_doc().root().is_html_document } fn children(self) -> AbstractNodeChildrenIterator<'a> { AbstractNodeChildrenIterator { current_node: self.first_child.get().map(|node| (*node.root()).clone()), } } fn child_elements(self) -> ChildElementIterator<'a> { self.children() .filter(|node| { node.is_element() }) .map(|node| { let elem: JSRef = ElementCast::to_ref(node).unwrap(); elem.clone() }) } fn wait_until_safe_to_modify_dom(self) { let document = self.owner_doc().root(); document.wait_until_safe_to_modify_dom(); } fn remove_self(self) { match self.parent_node().root() { Some(parent) => parent.remove_child(self), None => () } } fn get_unique_id(self) -> String { self.unique_id.borrow().clone() } fn summarize(self) -> NodeInfo { if self.unique_id.borrow().is_empty() { let mut unique_id = self.unique_id.borrow_mut(); *unique_id = uuid::Uuid::new_v4().to_simple_string(); } NodeInfo { uniqueId: self.unique_id.borrow().clone(), baseURI: self.GetBaseURI().unwrap_or("".to_string()), parent: self.GetParentNode().root().map(|node| node.get_unique_id()).unwrap_or("".to_string()), nodeType: self.NodeType() as uint, namespaceURI: "".to_string(), //FIXME nodeName: self.NodeName(), numChildren: self.ChildNodes().root().Length() as uint, //FIXME doctype nodes only name: "".to_string(), publicId: "".to_string(), systemId: "".to_string(), attrs: if self.is_element() { let elem: JSRef = ElementCast::to_ref(self).unwrap(); elem.summarize() } else { vec!() }, isDocumentElement: self.owner_doc().root() .GetDocumentElement() .map(|elem| NodeCast::from_ref(*elem.root()) == self) .unwrap_or(false), shortValue: self.GetNodeValue().unwrap_or("".to_string()), //FIXME: truncate incompleteValue: false, //FIXME: reflect truncation } } } /// If the given untrusted node address represents a valid DOM node in the given runtime, /// returns it. pub fn from_untrusted_node_address(runtime: *mut JSRuntime, candidate: UntrustedNodeAddress) -> Temporary { unsafe { let candidate: uintptr_t = mem::transmute(candidate); let object: *mut JSObject = jsfriendapi::bindgen::JS_GetAddressableObject(runtime, candidate); if object.is_null() { fail!("Attempted to create a `JS` from an invalid pointer!") } let boxed_node: *const Node = utils::unwrap(object); Temporary::new(JS::from_raw(boxed_node)) } } pub trait LayoutNodeHelpers { unsafe fn type_id_for_layout(&self) -> NodeTypeId; unsafe fn parent_node_ref(&self) -> Option>; unsafe fn first_child_ref(&self) -> Option>; unsafe fn last_child_ref(&self) -> Option>; unsafe fn prev_sibling_ref(&self) -> Option>; unsafe fn next_sibling_ref(&self) -> Option>; unsafe fn owner_doc_for_layout(&self) -> JS; unsafe fn is_element_for_layout(&self) -> bool; } impl LayoutNodeHelpers for JS { #[inline] unsafe fn type_id_for_layout(&self) -> NodeTypeId { (*self.unsafe_get()).type_id } #[inline] unsafe fn is_element_for_layout(&self) -> bool { (*self.unsafe_get()).is_element() } #[inline] unsafe fn parent_node_ref(&self) -> Option> { (*self.unsafe_get()).parent_node.get_inner() } #[inline] unsafe fn first_child_ref(&self) -> Option> { (*self.unsafe_get()).first_child.get_inner() } #[inline] unsafe fn last_child_ref(&self) -> Option> { (*self.unsafe_get()).last_child.get_inner() } #[inline] unsafe fn prev_sibling_ref(&self) -> Option> { (*self.unsafe_get()).prev_sibling.get_inner() } #[inline] unsafe fn next_sibling_ref(&self) -> Option> { (*self.unsafe_get()).next_sibling.get_inner() } #[inline] unsafe fn owner_doc_for_layout(&self) -> JS { (*self.unsafe_get()).owner_doc.get_inner().unwrap() } } pub trait RawLayoutNodeHelpers { unsafe fn get_hover_state_for_layout(&self) -> bool; unsafe fn get_disabled_state_for_layout(&self) -> bool; unsafe fn get_enabled_state_for_layout(&self) -> bool; fn type_id_for_layout(&self) -> NodeTypeId; } impl RawLayoutNodeHelpers for Node { unsafe fn get_hover_state_for_layout(&self) -> bool { (*self.unsafe_get_flags()).contains(InHoverState) } unsafe fn get_disabled_state_for_layout(&self) -> bool { (*self.unsafe_get_flags()).contains(InDisabledState) } unsafe fn get_enabled_state_for_layout(&self) -> bool { (*self.unsafe_get_flags()).contains(InEnabledState) } fn type_id_for_layout(&self) -> NodeTypeId { self.type_id } } // // Iteration and traversal // pub type ChildElementIterator<'a> = Map<'a, JSRef<'a, Node>, JSRef<'a, Element>, Filter<'a, JSRef<'a, Node>, AbstractNodeChildrenIterator<'a>>>; pub struct AbstractNodeChildrenIterator<'a> { current_node: Option>, } impl<'a> Iterator> for AbstractNodeChildrenIterator<'a> { fn next(&mut self) -> Option> { let node = self.current_node.clone(); self.current_node = node.clone().and_then(|node| { node.next_sibling().map(|node| (*node.root()).clone()) }); node } } pub struct AncestorIterator<'a> { current: Option>, } impl<'a> Iterator> for AncestorIterator<'a> { fn next(&mut self) -> Option> { if self.current.is_none() { return None; } // FIXME: Do we need two clones here? let x = self.current.as_ref().unwrap().clone(); self.current = x.parent_node().map(|node| (*node.root()).clone()); Some(x) } } // FIXME: Do this without precomputing a vector of refs. // Easy for preorder; harder for postorder. pub struct TreeIterator<'a> { nodes: Vec>, index: uint, } impl<'a> TreeIterator<'a> { fn new(nodes: Vec>) -> TreeIterator<'a> { TreeIterator { nodes: nodes, index: 0, } } } impl<'a> Iterator> for TreeIterator<'a> { fn next(&mut self) -> Option> { if self.index >= self.nodes.len() { None } else { let v = self.nodes[self.index]; let v = v.clone(); self.index += 1; Some(v) } } } #[must_root] pub struct NodeIterator { pub start_node: JS, pub current_node: Option>, pub depth: uint, include_start: bool, include_descendants_of_void: bool } impl NodeIterator { pub fn new<'a>(start_node: JSRef<'a, Node>, include_start: bool, include_descendants_of_void: bool) -> NodeIterator { NodeIterator { start_node: JS::from_rooted(start_node), current_node: None, depth: 0, include_start: include_start, include_descendants_of_void: include_descendants_of_void } } fn next_child<'b>(&self, node: JSRef<'b, Node>) -> Option> { if !self.include_descendants_of_void && node.is_element() { let elem: JSRef = ElementCast::to_ref(node).unwrap(); if elem.is_void() { None } else { node.first_child().map(|child| (*child.root()).clone()) } } else { node.first_child().map(|child| (*child.root()).clone()) } } } impl<'a> Iterator> for NodeIterator { fn next(&mut self) -> Option> { self.current_node = match self.current_node.as_ref().map(|node| node.root()) { None => { if self.include_start { Some(self.start_node) } else { self.next_child(*self.start_node.root()) .map(|child| JS::from_rooted(child)) } }, Some(node) => { match self.next_child(*node) { Some(child) => { self.depth += 1; Some(JS::from_rooted(child)) }, None if JS::from_rooted(*node) == self.start_node => None, None => { match node.next_sibling().root() { Some(sibling) => Some(JS::from_rooted(*sibling)), None => { let mut candidate = node.clone(); while candidate.next_sibling().is_none() { candidate = (*candidate.parent_node() .expect("Got to root without reaching start node") .root()).clone(); self.depth -= 1; if JS::from_rooted(candidate) == self.start_node { break; } } if JS::from_rooted(candidate) != self.start_node { candidate.next_sibling().map(|node| JS::from_rooted(*node.root())) } else { None } } } } } } }; self.current_node.map(|node| (*node.root()).clone()) } } fn gather_abstract_nodes<'a>(cur: JSRef<'a, Node>, refs: &mut Vec>, postorder: bool) { if !postorder { refs.push(cur.clone()); } for kid in cur.children() { gather_abstract_nodes(kid, refs, postorder) } if postorder { refs.push(cur.clone()); } } /// Specifies whether children must be recursively cloned or not. #[deriving(PartialEq)] pub enum CloneChildrenFlag { CloneChildren, DoNotCloneChildren } fn as_uintptr(t: &T) -> uintptr_t { t as *const T as uintptr_t } impl Node { pub fn reflect_node (node: Box, document: JSRef, wrap_fn: extern "Rust" fn(*mut JSContext, &GlobalRef, Box) -> Temporary) -> Temporary { let window = document.window.root(); reflect_dom_object(node, &global::Window(*window), wrap_fn) } pub fn new_inherited(type_id: NodeTypeId, doc: JSRef) -> Node { Node::new_(type_id, Some(doc.clone())) } pub fn new_without_doc(type_id: NodeTypeId) -> Node { Node::new_(type_id, None) } fn new_(type_id: NodeTypeId, doc: Option>) -> Node { Node { eventtarget: EventTarget::new_inherited(NodeTargetTypeId(type_id)), type_id: type_id, parent_node: Default::default(), first_child: Default::default(), last_child: Default::default(), next_sibling: Default::default(), prev_sibling: Default::default(), owner_doc: MutNullableJS::new(doc), child_list: Default::default(), flags: RefCell::new(NodeFlags::new(type_id)), layout_data: LayoutDataRef::new(), unique_id: RefCell::new("".to_string()), } } // http://dom.spec.whatwg.org/#concept-node-adopt pub fn adopt(node: JSRef, document: JSRef) { // Step 1. match node.parent_node().root() { Some(parent) => { Node::remove(node, *parent, Unsuppressed); } None => (), } // Step 2. let node_doc = document_from_node(node).root(); if *node_doc != document { for descendant in node.traverse_preorder() { descendant.set_owner_doc(document); } } // Step 3. // If node is an element, it is _affected by a base URL change_. } // http://dom.spec.whatwg.org/#concept-node-pre-insert fn pre_insert(node: JSRef, parent: JSRef, child: Option>) -> Fallible> { // 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. match child { Some(child) if !parent.is_parent_of(child) => return Err(NotFound), _ => () } // Step 4-5. match node.type_id() { TextNodeTypeId => { match node.parent_node().root() { Some(ref parent) if parent.is_document() => return Err(HierarchyRequest), _ => () } } DoctypeNodeTypeId => { match node.parent_node().root() { Some(ref parent) if !parent.is_document() => return Err(HierarchyRequest), _ => () } } DocumentFragmentNodeTypeId | ElementNodeTypeId(_) | 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().count() { 0 => (), // Step 6.1.2 1 => { // FIXME: change to empty() when https://github.com/mozilla/rust/issues/11218 // will be fixed if parent.child_elements().count() > 0 { return Err(HierarchyRequest); } match child { Some(child) => { if child.inclusively_following_siblings() .any(|child| child.is_doctype()) { return Err(HierarchyRequest) } } _ => (), } }, // Step 6.1.1(a) _ => return Err(HierarchyRequest), } }, // Step 6.2 ElementNodeTypeId(_) => { // FIXME: change to empty() when https://github.com/mozilla/rust/issues/11218 // will be fixed if parent.child_elements().count() > 0 { return Err(HierarchyRequest); } match child { Some(ref child) => { if child.inclusively_following_siblings() .any(|child| child.is_doctype()) { return Err(HierarchyRequest) } } _ => (), } }, // Step 6.3 DoctypeNodeTypeId => { if parent.children().any(|c| c.is_doctype()) { return Err(HierarchyRequest); } match child { Some(ref child) => { if parent.children() .take_while(|c| c != child) .any(|c| c.is_element()) { return Err(HierarchyRequest); } }, None => { // FIXME: change to empty() when https://github.com/mozilla/rust/issues/11218 // will be fixed if parent.child_elements().count() > 0 { return Err(HierarchyRequest); } }, } }, TextNodeTypeId | ProcessingInstructionNodeTypeId | CommentNodeTypeId => (), DocumentNodeTypeId => unreachable!(), } }, _ => (), } // Step 7-8. let referenceChild = match child { Some(child) if child == node => node.next_sibling().map(|node| (*node.root()).clone()), _ => child }; // Step 9. let document = document_from_node(parent).root(); Node::adopt(node, *document); // Step 10. Node::insert(node, parent, referenceChild, Unsuppressed); // Step 11. return Ok(Temporary::from_rooted(node)) } // http://dom.spec.whatwg.org/#concept-node-insert fn insert(node: JSRef, parent: JSRef, child: Option>, suppress_observers: SuppressObserver) { // XXX assert owner_doc // Step 1-3: ranges. // Step 4. let mut nodes = match node.type_id() { DocumentFragmentNodeTypeId => node.children().collect(), _ => vec!(node.clone()), }; // Step 5: DocumentFragment, mutation records. // Step 6: DocumentFragment. match node.type_id() { DocumentFragmentNodeTypeId => { for c in node.children() { Node::remove(c, node, Suppressed); } }, _ => (), } // Step 7: mutation records. // Step 8. for node in nodes.iter_mut() { parent.add_child(*node, child); let is_in_doc = parent.is_in_doc(); for kid in node.traverse_preorder() { if is_in_doc { kid.flags.borrow_mut().insert(IsInDoc); } else { kid.flags.borrow_mut().remove(IsInDoc); } } } // Step 9. match suppress_observers { Unsuppressed => { for node in nodes.iter() { node.node_inserted(); } } Suppressed => () } } // http://dom.spec.whatwg.org/#concept-node-replace-all fn replace_all(node: Option>, parent: JSRef) { // Step 1. match node { Some(node) => { let document = document_from_node(parent).root(); Node::adopt(node, *document); } None => (), } // Step 2. let removedNodes: Vec> = parent.children().collect(); // Step 3. let addedNodes = match node { None => vec!(), Some(node) => match node.type_id() { DocumentFragmentNodeTypeId => node.children().collect(), _ => vec!(node.clone()), }, }; // Step 4. for child in parent.children() { Node::remove(child, parent, Suppressed); } // Step 5. match node { Some(node) => Node::insert(node, parent, None, Suppressed), None => (), } // Step 6: mutation records. // Step 7. let parent_in_doc = parent.is_in_doc(); for removedNode in removedNodes.iter() { removedNode.node_removed(parent_in_doc); } for addedNode in addedNodes.iter() { addedNode.node_inserted(); } } // http://dom.spec.whatwg.org/#concept-node-pre-remove fn pre_remove(child: JSRef, parent: JSRef) -> Fallible> { // Step 1. match child.parent_node() { Some(ref node) if node != &Temporary::from_rooted(parent) => return Err(NotFound), _ => () } // Step 2. Node::remove(child, parent, Unsuppressed); // Step 3. Ok(Temporary::from_rooted(child)) } // http://dom.spec.whatwg.org/#concept-node-remove fn remove(node: JSRef, parent: JSRef, suppress_observers: SuppressObserver) { assert!(node.parent_node().map_or(false, |node_parent| node_parent == Temporary::from_rooted(parent))); // Step 1-5: ranges. // Step 6-7: mutation observers. // Step 8. parent.remove_child(node); node.flags.borrow_mut().remove(IsInDoc); // Step 9. match suppress_observers { Suppressed => (), Unsuppressed => node.node_removed(parent.is_in_doc()), } } // http://dom.spec.whatwg.org/#concept-node-clone pub fn clone(node: JSRef, maybe_doc: Option>, clone_children: CloneChildrenFlag) -> Temporary { // Step 1. let document = match maybe_doc { Some(doc) => JS::from_rooted(doc).root(), None => node.owner_doc().root() }; // Step 2. // XXXabinader: clone() for each node as trait? let copy: Root = match node.type_id() { DoctypeNodeTypeId => { let doctype: JSRef = DocumentTypeCast::to_ref(node).unwrap(); let doctype = DocumentType::new(doctype.name.clone(), Some(doctype.public_id.clone()), Some(doctype.system_id.clone()), *document); NodeCast::from_temporary(doctype) }, DocumentFragmentNodeTypeId => { let doc_fragment = DocumentFragment::new(*document); NodeCast::from_temporary(doc_fragment) }, CommentNodeTypeId => { let comment: JSRef = CommentCast::to_ref(node).unwrap(); let comment = Comment::new(comment.characterdata.data.borrow().clone(), *document); NodeCast::from_temporary(comment) }, DocumentNodeTypeId => { let document: JSRef = DocumentCast::to_ref(node).unwrap(); let is_html_doc = match document.is_html_document { true => HTMLDocument, false => NonHTMLDocument }; let window = document.window.root(); let document = Document::new(*window, Some(document.url().clone()), is_html_doc, None); NodeCast::from_temporary(document) }, ElementNodeTypeId(..) => { let element: JSRef = ElementCast::to_ref(node).unwrap(); let element = build_element_from_tag(element.local_name.as_slice().to_string(), element.namespace.clone(), Some(element.prefix.as_slice().to_string()), *document); NodeCast::from_temporary(element) }, TextNodeTypeId => { let text: JSRef = TextCast::to_ref(node).unwrap(); let text = Text::new(text.characterdata.data.borrow().clone(), *document); NodeCast::from_temporary(text) }, ProcessingInstructionNodeTypeId => { let pi: JSRef = ProcessingInstructionCast::to_ref(node).unwrap(); let pi = ProcessingInstruction::new(pi.target.clone(), pi.characterdata.data.borrow().clone(), *document); NodeCast::from_temporary(pi) }, }.root(); // Step 3. let document = if copy.is_document() { let doc: JSRef = DocumentCast::to_ref(*copy).unwrap(); JS::from_rooted(doc).root() } else { JS::from_rooted(*document).root() }; assert!(&*copy.owner_doc().root() == &*document); // Step 4 (some data already copied in step 2). match node.type_id() { DocumentNodeTypeId => { let node_doc: JSRef = DocumentCast::to_ref(node).unwrap(); let copy_doc: JSRef = DocumentCast::to_ref(*copy).unwrap(); copy_doc.set_encoding_name(node_doc.encoding_name.borrow().clone()); copy_doc.set_quirks_mode(node_doc.quirks_mode()); }, ElementNodeTypeId(..) => { let node_elem: JSRef = ElementCast::to_ref(node).unwrap(); let copy_elem: JSRef = ElementCast::to_ref(*copy).unwrap(); // FIXME: https://github.com/mozilla/servo/issues/1737 let window = document.window.root(); for attr in node_elem.attrs.borrow().iter().map(|attr| attr.root()) { copy_elem.attrs.borrow_mut().push_unrooted( &Attr::new(*window, attr.local_name().clone(), attr.value().clone(), attr.name.clone(), attr.namespace.clone(), attr.prefix.clone(), copy_elem)); } }, _ => () } // Step 5: cloning steps. // Step 6. if clone_children == CloneChildren { for child in node.children() { let child_copy = Node::clone(child, Some(*document), clone_children).root(); let _inserted_node = Node::pre_insert(*child_copy, *copy, None); } } // Step 7. Temporary::from_rooted(*copy) } /// Sends layout data, if any, back to the layout task to be destroyed. unsafe fn reap_layout_data(&mut self) { if self.layout_data.is_present() { let layout_data = mem::replace(&mut self.layout_data, LayoutDataRef::new()); let layout_chan = layout_data.take_chan(); match layout_chan { None => {} Some(chan) => { let LayoutChan(chan) = chan; chan.send(ReapLayoutDataMsg(layout_data)) }, } } } pub unsafe fn unsafe_get_flags(&self) -> *const NodeFlags { mem::transmute(&self.flags) } pub fn collect_text_contents<'a, T: Iterator>>(mut iterator: T) -> String { let mut content = String::new(); for node in iterator { let text: Option> = TextCast::to_ref(node); match text { Some(text) => content.push_str(text.characterdata.data.borrow().as_slice()), None => (), } } content } } impl<'a> NodeMethods for JSRef<'a, Node> { // http://dom.spec.whatwg.org/#dom-node-nodetype fn NodeType(self) -> u16 { match self.type_id { ElementNodeTypeId(_) => NodeConstants::ELEMENT_NODE, TextNodeTypeId => NodeConstants::TEXT_NODE, ProcessingInstructionNodeTypeId => NodeConstants::PROCESSING_INSTRUCTION_NODE, CommentNodeTypeId => NodeConstants::COMMENT_NODE, DocumentNodeTypeId => NodeConstants::DOCUMENT_NODE, DoctypeNodeTypeId => NodeConstants::DOCUMENT_TYPE_NODE, DocumentFragmentNodeTypeId => NodeConstants::DOCUMENT_FRAGMENT_NODE, } } // http://dom.spec.whatwg.org/#dom-node-nodename fn NodeName(self) -> DOMString { match self.type_id { ElementNodeTypeId(..) => { let elem: JSRef = ElementCast::to_ref(self).unwrap(); elem.TagName() } TextNodeTypeId => "#text".to_string(), ProcessingInstructionNodeTypeId => { let processing_instruction: JSRef = ProcessingInstructionCast::to_ref(self).unwrap(); processing_instruction.Target() } CommentNodeTypeId => "#comment".to_string(), DoctypeNodeTypeId => { let doctype: JSRef = DocumentTypeCast::to_ref(self).unwrap(); doctype.name.clone() }, DocumentFragmentNodeTypeId => "#document-fragment".to_string(), DocumentNodeTypeId => "#document".to_string() } } // http://dom.spec.whatwg.org/#dom-node-baseuri fn GetBaseURI(self) -> Option { // FIXME (#1824) implement. None } // http://dom.spec.whatwg.org/#dom-node-ownerdocument fn GetOwnerDocument(self) -> Option> { match self.type_id { ElementNodeTypeId(..) | CommentNodeTypeId | TextNodeTypeId | ProcessingInstructionNodeTypeId | DoctypeNodeTypeId | DocumentFragmentNodeTypeId => Some(self.owner_doc()), DocumentNodeTypeId => None } } // http://dom.spec.whatwg.org/#dom-node-parentnode fn GetParentNode(self) -> Option> { self.parent_node.get() } // http://dom.spec.whatwg.org/#dom-node-parentelement fn GetParentElement(self) -> Option> { self.parent_node.get() .and_then(|parent| { let parent = parent.root(); ElementCast::to_ref(*parent).map(|elem| { Temporary::from_rooted(elem) }) }) } // http://dom.spec.whatwg.org/#dom-node-haschildnodes fn HasChildNodes(self) -> bool { self.first_child.get().is_some() } // http://dom.spec.whatwg.org/#dom-node-childnodes fn ChildNodes(self) -> Temporary { match self.child_list.get() { None => (), Some(list) => return list, } let doc = self.owner_doc().root(); let window = doc.window.root(); let child_list = NodeList::new_child_list(*window, self); self.child_list.assign(Some(child_list)); self.child_list.get().unwrap() } // http://dom.spec.whatwg.org/#dom-node-firstchild fn GetFirstChild(self) -> Option> { self.first_child.get() } // http://dom.spec.whatwg.org/#dom-node-lastchild fn GetLastChild(self) -> Option> { self.last_child.get() } // http://dom.spec.whatwg.org/#dom-node-previoussibling fn GetPreviousSibling(self) -> Option> { self.prev_sibling.get() } // http://dom.spec.whatwg.org/#dom-node-nextsibling fn GetNextSibling(self) -> Option> { self.next_sibling.get() } // http://dom.spec.whatwg.org/#dom-node-nodevalue fn GetNodeValue(self) -> Option { match self.type_id { CommentNodeTypeId | TextNodeTypeId | ProcessingInstructionNodeTypeId => { let chardata: JSRef = CharacterDataCast::to_ref(self).unwrap(); Some(chardata.Data()) } _ => { None } } } // http://dom.spec.whatwg.org/#dom-node-nodevalue fn SetNodeValue(self, val: Option) { match self.type_id { CommentNodeTypeId | TextNodeTypeId | ProcessingInstructionNodeTypeId => { self.SetTextContent(val) } _ => {} } } // http://dom.spec.whatwg.org/#dom-node-textcontent fn GetTextContent(self) -> Option { match self.type_id { DocumentFragmentNodeTypeId | ElementNodeTypeId(..) => { let content = Node::collect_text_contents(self.traverse_preorder()); Some(content) } CommentNodeTypeId | TextNodeTypeId | ProcessingInstructionNodeTypeId => { let characterdata: JSRef = CharacterDataCast::to_ref(self).unwrap(); Some(characterdata.Data()) } DoctypeNodeTypeId | DocumentNodeTypeId => { None } } } // http://dom.spec.whatwg.org/#dom-node-textcontent fn SetTextContent(self, value: Option) { let value = null_str_as_empty(&value); match self.type_id { DocumentFragmentNodeTypeId | ElementNodeTypeId(..) => { // Step 1-2. let node = if value.len() == 0 { None } else { let document = self.owner_doc().root(); Some(NodeCast::from_temporary(document.CreateTextNode(value))) }.root(); // Step 3. Node::replace_all(node.root_ref(), self); } CommentNodeTypeId | TextNodeTypeId | ProcessingInstructionNodeTypeId => { self.wait_until_safe_to_modify_dom(); let characterdata: JSRef = CharacterDataCast::to_ref(self).unwrap(); *characterdata.data.borrow_mut() = value; // Notify the document that the content of this node is different let document = self.owner_doc().root(); document.content_changed(); } DoctypeNodeTypeId | DocumentNodeTypeId => {} } } // http://dom.spec.whatwg.org/#dom-node-insertbefore fn InsertBefore(self, node: JSRef, child: Option>) -> Fallible> { Node::pre_insert(node, self, child) } // http://dom.spec.whatwg.org/#dom-node-appendchild fn AppendChild(self, node: JSRef) -> Fallible> { Node::pre_insert(node, self, None) } // http://dom.spec.whatwg.org/#concept-node-replace fn ReplaceChild(self, node: JSRef, child: JSRef) -> Fallible> { // Step 1. match self.type_id { DocumentNodeTypeId | DocumentFragmentNodeTypeId | ElementNodeTypeId(..) => (), _ => return Err(HierarchyRequest) } // Step 2. if node.is_inclusive_ancestor_of(self) { return Err(HierarchyRequest); } // Step 3. if !self.is_parent_of(child) { return Err(NotFound); } // Step 4-5. match node.type_id() { TextNodeTypeId if self.is_document() => return Err(HierarchyRequest), DoctypeNodeTypeId if !self.is_document() => return Err(HierarchyRequest), DocumentFragmentNodeTypeId | DoctypeNodeTypeId | ElementNodeTypeId(..) | TextNodeTypeId | ProcessingInstructionNodeTypeId | CommentNodeTypeId => (), DocumentNodeTypeId => return Err(HierarchyRequest) } // Step 6. match self.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().count() { 0 => (), // Step 6.1.2 1 => { if self.child_elements().any(|c| NodeCast::from_ref(c) != child) { return Err(HierarchyRequest); } if child.following_siblings() .any(|child| child.is_doctype()) { return Err(HierarchyRequest); } }, // Step 6.1.1(a) _ => return Err(HierarchyRequest) } }, // Step 6.2 ElementNodeTypeId(..) => { if self.child_elements().any(|c| NodeCast::from_ref(c) != child) { return Err(HierarchyRequest); } if child.following_siblings() .any(|child| child.is_doctype()) { return Err(HierarchyRequest); } }, // Step 6.3 DoctypeNodeTypeId => { if self.children().any(|c| c.is_doctype() && c != child) { return Err(HierarchyRequest); } if self.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(Temporary::from_rooted(child)); } // Step 7-8. let next_sibling = child.next_sibling().map(|node| (*node.root()).clone()); let reference_child = match next_sibling { Some(sibling) if sibling == node => node.next_sibling().map(|node| (*node.root()).clone()), _ => next_sibling }; // Step 9. let document = document_from_node(self).root(); Node::adopt(node, *document); { // Step 10. Node::remove(child, self, Suppressed); // Step 11. Node::insert(node, self, reference_child, Suppressed); } // Step 12-14. // Step 13: mutation records. child.node_removed(self.is_in_doc()); if node.type_id() == DocumentFragmentNodeTypeId { for child_node in node.children() { child_node.node_inserted(); } } else { node.node_inserted(); } // Step 15. Ok(Temporary::from_rooted(child)) } // http://dom.spec.whatwg.org/#dom-node-removechild fn RemoveChild(self, node: JSRef) -> Fallible> { Node::pre_remove(node, self) } // http://dom.spec.whatwg.org/#dom-node-normalize fn Normalize(self) { let mut prev_text = None; for child in self.children() { if child.is_text() { let characterdata: JSRef = CharacterDataCast::to_ref(child).unwrap(); if characterdata.Length() == 0 { self.remove_child(child); } else { match prev_text { Some(text_node) => { let prev_characterdata: JSRef = CharacterDataCast::to_ref(text_node).unwrap(); let _ = prev_characterdata.AppendData(characterdata.Data()); self.remove_child(child); }, None => prev_text = Some(child) } } } else { child.Normalize(); prev_text = None; } } } // http://dom.spec.whatwg.org/#dom-node-clonenode fn CloneNode(self, deep: bool) -> Temporary { match deep { true => Node::clone(self, None, CloneChildren), false => Node::clone(self, None, DoNotCloneChildren) } } // http://dom.spec.whatwg.org/#dom-node-isequalnode fn IsEqualNode(self, maybe_node: Option>) -> bool { fn is_equal_doctype(node: JSRef, other: JSRef) -> bool { let doctype: JSRef = DocumentTypeCast::to_ref(node).unwrap(); let other_doctype: JSRef = DocumentTypeCast::to_ref(other).unwrap(); (doctype.name == other_doctype.name) && (doctype.public_id == other_doctype.public_id) && (doctype.system_id == other_doctype.system_id) } fn is_equal_element(node: JSRef, other: JSRef) -> bool { let element: JSRef = ElementCast::to_ref(node).unwrap(); let other_element: JSRef = ElementCast::to_ref(other).unwrap(); // FIXME: namespace prefix (element.namespace == other_element.namespace) && (element.local_name == other_element.local_name) && (element.attrs.borrow().len() == other_element.attrs.borrow().len()) } fn is_equal_processinginstruction(node: JSRef, other: JSRef) -> bool { let pi: JSRef = ProcessingInstructionCast::to_ref(node).unwrap(); let other_pi: JSRef = ProcessingInstructionCast::to_ref(other).unwrap(); (pi.target == other_pi.target) && (*pi.characterdata.data.borrow() == *other_pi.characterdata.data.borrow()) } fn is_equal_characterdata(node: JSRef, other: JSRef) -> bool { let characterdata: JSRef = CharacterDataCast::to_ref(node).unwrap(); let other_characterdata: JSRef = CharacterDataCast::to_ref(other).unwrap(); *characterdata.data.borrow() == *other_characterdata.data.borrow() } fn is_equal_element_attrs(node: JSRef, other: JSRef) -> bool { let element: JSRef = ElementCast::to_ref(node).unwrap(); let other_element: JSRef = ElementCast::to_ref(other).unwrap(); assert!(element.attrs.borrow().len() == other_element.attrs.borrow().len()); element.attrs.borrow().iter().map(|attr| attr.root()).all(|attr| { other_element.attrs.borrow().iter().map(|attr| attr.root()).any(|other_attr| { (attr.namespace == other_attr.namespace) && (attr.local_name() == other_attr.local_name()) && (attr.value().as_slice() == other_attr.value().as_slice()) }) }) } fn is_equal_node(this: JSRef, node: JSRef) -> bool { // Step 2. if this.type_id() != node.type_id() { return false; } match node.type_id() { // Step 3. DoctypeNodeTypeId if !is_equal_doctype(this, node) => return false, ElementNodeTypeId(..) if !is_equal_element(this, node) => return false, ProcessingInstructionNodeTypeId if !is_equal_processinginstruction(this, node) => return false, TextNodeTypeId | CommentNodeTypeId if !is_equal_characterdata(this, node) => return false, // Step 4. ElementNodeTypeId(..) if !is_equal_element_attrs(this, node) => return false, _ => () } // Step 5. if this.children().count() != node.children().count() { return false; } // Step 6. this.children().zip(node.children()).all(|(ref child, ref other_child)| { is_equal_node(*child, *other_child) }) } match maybe_node { // Step 1. None => false, // Step 2-6. Some(node) => is_equal_node(self, node) } } // http://dom.spec.whatwg.org/#dom-node-comparedocumentposition fn CompareDocumentPosition(self, other: JSRef) -> u16 { if self == other { // step 2. 0 } else { let mut lastself = self.clone(); let mut lastother = other.clone(); for ancestor in self.ancestors() { if ancestor == other { // step 4. return NodeConstants::DOCUMENT_POSITION_CONTAINS + NodeConstants::DOCUMENT_POSITION_PRECEDING; } lastself = ancestor.clone(); } for ancestor in other.ancestors() { if ancestor == self { // step 5. return NodeConstants::DOCUMENT_POSITION_CONTAINED_BY + NodeConstants::DOCUMENT_POSITION_FOLLOWING; } lastother = ancestor.clone(); } if lastself != lastother { let abstract_uint: uintptr_t = as_uintptr(&self); let other_uint: uintptr_t = as_uintptr(&*other); let random = if abstract_uint < other_uint { NodeConstants::DOCUMENT_POSITION_FOLLOWING } else { NodeConstants::DOCUMENT_POSITION_PRECEDING }; // step 3. return random + NodeConstants::DOCUMENT_POSITION_DISCONNECTED + NodeConstants::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; } for child in lastself.traverse_preorder() { if child == other { // step 6. return NodeConstants::DOCUMENT_POSITION_PRECEDING; } if child == self { // step 7. return NodeConstants::DOCUMENT_POSITION_FOLLOWING; } } unreachable!() } } // http://dom.spec.whatwg.org/#dom-node-contains fn Contains(self, maybe_other: Option>) -> bool { match maybe_other { None => false, Some(other) => self.is_inclusive_ancestor_of(other) } } // http://dom.spec.whatwg.org/#dom-node-lookupprefix fn LookupPrefix(self, _prefix: Option) -> Option { // FIXME (#1826) implement. None } // http://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri fn LookupNamespaceURI(self, _namespace: Option) -> Option { // FIXME (#1826) implement. None } // http://dom.spec.whatwg.org/#dom-node-isdefaultnamespace fn IsDefaultNamespace(self, _namespace: Option) -> bool { // FIXME (#1826) implement. false } } impl Reflectable for Node { fn reflector<'a>(&'a self) -> &'a Reflector { self.eventtarget.reflector() } } pub fn document_from_node(derived: JSRef) -> Temporary { let node: JSRef = NodeCast::from_ref(derived); node.owner_doc() } pub fn window_from_node(derived: JSRef) -> Temporary { let document = document_from_node(derived).root(); Temporary::new(document.window.clone()) } impl<'a> VirtualMethods for JSRef<'a, Node> { fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { let eventtarget: &JSRef = EventTargetCast::from_borrowed_ref(self); Some(eventtarget as &VirtualMethods) } } impl<'a> style::TNode<'a, JSRef<'a, Element>> for JSRef<'a, Node> { fn parent_node(self) -> Option> { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. fn parent_node<'a, T: NodeHelpers<'a>>(this: T) -> Option> { this.parent_node() } parent_node(self).map(|node| *node.root()) } fn first_child(self) -> Option> { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. fn first_child<'a, T: NodeHelpers<'a>>(this: T) -> Option> { this.first_child() } first_child(self).map(|node| *node.root()) } fn prev_sibling(self) -> Option> { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. fn prev_sibling<'a, T: NodeHelpers<'a>>(this: T) -> Option> { this.prev_sibling() } prev_sibling(self).map(|node| *node.root()) } fn next_sibling(self) -> Option> { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. fn next_sibling<'a, T: NodeHelpers<'a>>(this: T) -> Option> { this.next_sibling() } next_sibling(self).map(|node| *node.root()) } fn is_document(self) -> bool { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. fn is_document<'a, T: NodeHelpers<'a>>(this: T) -> bool { this.is_document() } is_document(self) } fn is_element(self) -> bool { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. fn is_element<'a, T: NodeHelpers<'a>>(this: T) -> bool { this.is_element() } is_element(self) } fn as_element(self) -> JSRef<'a, Element> { let elem: Option> = ElementCast::to_ref(self); assert!(elem.is_some()); elem.unwrap() } fn match_attr(self, attr: &style::AttrSelector, test: |&str| -> bool) -> bool { let name = { if self.is_html_element_in_html_document() { attr.lower_name.as_slice() } else { attr.name.as_slice() } }; match attr.namespace { style::SpecificNamespace(ref ns) => { self.as_element().get_attribute(ns.clone(), name).root() .map_or(false, |attr| test(attr.value().as_slice())) }, style::AnyNamespace => { self.as_element().get_attributes(name).iter() .map(|attr| attr.root()) .any(|attr| test(attr.value().as_slice())) } } } fn is_html_element_in_html_document(self) -> bool { let elem: Option> = ElementCast::to_ref(self); assert!(elem.is_some()); elem.unwrap().html_element_in_html_document() } } pub trait DisabledStateHelpers { fn check_ancestors_disabled_state_for_form_control(self); fn check_parent_disabled_state_for_option(self); fn check_disabled_attribute(self); } impl<'a> DisabledStateHelpers for JSRef<'a, Node> { fn check_ancestors_disabled_state_for_form_control(self) { if self.get_disabled_state() { return; } for ancestor in self.ancestors().filter(|ancestor| ancestor.is_htmlfieldsetelement()) { if !ancestor.get_disabled_state() { continue; } if ancestor.is_parent_of(self) { self.set_disabled_state(true); self.set_enabled_state(false); return; } match ancestor.children().find(|child| child.is_htmllegendelement()) { Some(legend) => { // XXXabinader: should we save previous ancestor to avoid this iteration? if self.ancestors().any(|ancestor| ancestor == legend) { continue; } }, None => () } self.set_disabled_state(true); self.set_enabled_state(false); return; } } fn check_parent_disabled_state_for_option(self) { if self.get_disabled_state() { return; } match self.parent_node().root() { Some(ref parent) if parent.is_htmloptgroupelement() && parent.get_disabled_state() => { self.set_disabled_state(true); self.set_enabled_state(false); }, _ => () } } fn check_disabled_attribute(self) { let elem: JSRef<'a, Element> = ElementCast::to_ref(self).unwrap(); let has_disabled_attrib = elem.has_attribute("disabled"); self.set_disabled_state(has_disabled_attrib); self.set_enabled_state(!has_disabled_attrib); } }