diff options
Diffstat (limited to 'components/script/dom/node.rs')
-rw-r--r-- | components/script/dom/node.rs | 2085 |
1 files changed, 2085 insertions, 0 deletions
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs new file mode 100644 index 00000000000..96ee5f62ba2 --- /dev/null +++ b/components/script/dom/node.rs @@ -0,0 +1,2085 @@ +/* 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; +use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; +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::NodeBinding::{NodeConstants, NodeMethods}; +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, Window}; +use dom::bindings::js::{JS, JSRef, RootedReference, Temporary, Root, OptionalUnrootable}; +use dom::bindings::js::{OptionalSettable, TemporaryPushable, OptionalRootedRootable}; +use dom::bindings::js::{ResultRootable, OptionalRootable}; +use dom::bindings::trace::Traceable; +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 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, JSRuntime}; +use js::jsfriendapi; +use libc; +use libc::uintptr_t; +use std::cell::{Cell, RefCell, Ref, RefMut}; +use std::iter::{Map, Filter}; +use std::mem; +use style; +use style::ComputedValues; +use sync::Arc; + +use serialize::{Encoder, Encodable}; + +// +// The basic Node structure +// + +/// An HTML node. +#[deriving(Encodable)] +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: Cell<Option<JS<Node>>>, + + /// The first child of this node. + first_child: Cell<Option<JS<Node>>>, + + /// The last child of this node. + last_child: Cell<Option<JS<Node>>>, + + /// The next sibling of this node. + next_sibling: Cell<Option<JS<Node>>>, + + /// The previous sibling of this node. + prev_sibling: Cell<Option<JS<Node>>>, + + /// The document that this node belongs to. + owner_doc: Cell<Option<JS<Document>>>, + + /// The live list of children return by .childNodes. + child_list: Cell<Option<JS<NodeList>>>, + + /// A bitfield of flags for node items. + flags: Traceable<RefCell<NodeFlags>>, + + /// 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, +} + +impl<S: Encoder<E>, E> Encodable<S, E> for LayoutDataRef { + fn encode(&self, _s: &mut S) -> Result<(), E> { + Ok(()) + } +} + +impl NodeDerived for EventTarget { + fn is_node(&self) -> bool { + match self.type_id { + NodeTargetTypeId(_) => true, + _ => false + } + } +} + +bitflags! { + #[doc = "Flags for node items."] + #[deriving(Encodable)] + 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<Arc<ComputedValues>>, +} + +/// Encapsulates the abstract layout data. +pub struct LayoutData { + chan: Option<LayoutChan>, + _shared_data: SharedLayoutData, + _data: *const (), +} + +pub struct LayoutDataRef { + pub data_cell: RefCell<Option<LayoutData>>, +} + +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<LayoutChan> { + let mut layout_data = self.data_cell.borrow_mut(); + match *layout_data { + None => None, + Some(..) => Some(layout_data.get_mut_ref().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<LayoutData> { + 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<LayoutData>> { + 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<LayoutData>> { + self.data_cell.borrow_mut() + } +} + +/// The different types of nodes. +#[deriving(PartialEq,Encodable)] +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<Node>, before: Option<JSRef<Node>>); + fn remove_child(&self, child: &JSRef<Node>); +} + +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.deref().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.deref().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<Node>, before: Option<JSRef<Node>>) { + let doc = self.owner_doc().root(); + doc.deref().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(ref 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(ref 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<Node>) { + let doc = self.owner_doc().root(); + doc.deref().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(ref 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(ref next_sibling) => { + next_sibling.prev_sibling.assign(child.prev_sibling.get()); + } + } + + child.prev_sibling.set(None); + child.next_sibling.set(None); + child.parent_node.set(None); + } +} + +pub trait NodeHelpers<'m, 'n> { + fn ancestors(&self) -> AncestorIterator<'n>; + fn children(&self) -> AbstractNodeChildrenIterator<'n>; + fn child_elements(&self) -> ChildElementIterator<'m, 'n>; + fn following_siblings(&self) -> AbstractNodeChildrenIterator<'n>; + fn is_in_doc(&self) -> bool; + fn is_inclusive_ancestor_of(&self, parent: &JSRef<Node>) -> bool; + fn is_parent_of(&self, child: &JSRef<Node>) -> bool; + + fn type_id(&self) -> NodeTypeId; + + fn parent_node(&self) -> Option<Temporary<Node>>; + fn first_child(&self) -> Option<Temporary<Node>>; + fn last_child(&self) -> Option<Temporary<Node>>; + fn prev_sibling(&self) -> Option<Temporary<Node>>; + fn next_sibling(&self) -> Option<Temporary<Node>>; + + fn owner_doc(&self) -> Temporary<Document>; + fn set_owner_doc(&self, document: &JSRef<Document>); + 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<'n>; + fn sequential_traverse_postorder(&self) -> TreeIterator<'n>; + fn inclusively_following_siblings(&self) -> AbstractNodeChildrenIterator<'n>; + + fn to_trusted_node_address(&self) -> TrustedNodeAddress; + + fn get_bounding_content_box(&self) -> Rect<Au>; + fn get_content_boxes(&self) -> Vec<Rect<Au>>; + + fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>>; + fn query_selector_all(&self, selectors: DOMString) -> Fallible<Temporary<NodeList>>; + + fn remove_self(&self); +} + +impl<'m, 'n> NodeHelpers<'m, 'n> for JSRef<'n, 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.deref().flags.deref().borrow().contains(IsInDoc) + } + + /// Returns the type ID of this node. Fails if this node is borrowed mutably. + fn type_id(&self) -> NodeTypeId { + self.deref().type_id + } + + fn parent_node(&self) -> Option<Temporary<Node>> { + self.deref().parent_node.get().map(|node| Temporary::new(node)) + } + + fn first_child(&self) -> Option<Temporary<Node>> { + self.deref().first_child.get().map(|node| Temporary::new(node)) + } + + fn last_child(&self) -> Option<Temporary<Node>> { + self.deref().last_child.get().map(|node| Temporary::new(node)) + } + + /// Returns the previous sibling of this node. Fails if this node is borrowed mutably. + fn prev_sibling(&self) -> Option<Temporary<Node>> { + self.deref().prev_sibling.get().map(|node| Temporary::new(node)) + } + + /// Returns the next sibling of this node. Fails if this node is borrowed mutably. + fn next_sibling(&self) -> Option<Temporary<Node>> { + self.deref().next_sibling.get().map(|node| Temporary::new(node)) + } + + #[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.deref().borrow().contains(InHoverState) + } + + fn set_hover_state(&self, state: bool) { + if state { + self.flags.deref().borrow_mut().insert(InHoverState); + } else { + self.flags.deref().borrow_mut().remove(InHoverState); + } + } + + fn get_disabled_state(&self) -> bool { + self.flags.deref().borrow().contains(InDisabledState) + } + + fn set_disabled_state(&self, state: bool) { + if state { + self.flags.deref().borrow_mut().insert(InDisabledState); + } else { + self.flags.deref().borrow_mut().remove(InDisabledState); + } + } + + fn get_enabled_state(&self) -> bool { + self.flags.deref().borrow().contains(InEnabledState) + } + + fn set_enabled_state(&self, state: bool) { + if state { + self.flags.deref().borrow_mut().insert(InEnabledState); + } else { + self.flags.deref().borrow_mut().remove(InEnabledState); + } + } + + /// Iterates over this node and all its descendants, in preorder. + fn traverse_preorder(&self) -> TreeIterator<'n> { + 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<'n> { + let mut nodes = vec!(); + gather_abstract_nodes(self, &mut nodes, true); + TreeIterator::new(nodes) + } + + fn inclusively_following_siblings(&self) -> AbstractNodeChildrenIterator<'n> { + AbstractNodeChildrenIterator { + current_node: Some(self.clone()), + } + } + + fn is_inclusive_ancestor_of(&self, parent: &JSRef<Node>) -> bool { + self == parent || parent.ancestors().any(|ancestor| &ancestor == self) + } + + fn following_siblings(&self) -> AbstractNodeChildrenIterator<'n> { + AbstractNodeChildrenIterator { + current_node: self.next_sibling().root().map(|next| next.deref().clone()), + } + } + + fn is_parent_of(&self, child: &JSRef<Node>) -> 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<Au> { + let window = window_from_node(self).root(); + let page = window.deref().page(); + let addr = self.to_trusted_node_address(); + + let ContentBoxResponse(rect) = page.layout_rpc.content_box(addr); + rect + } + + fn get_content_boxes(&self) -> Vec<Rect<Au>> { + let window = window_from_node(self).root(); + let page = window.deref().page(); + let addr = self.to_trusted_node_address(); + let ContentBoxesResponse(rects) = page.layout_rpc.content_boxes(addr); + rects + } + + // http://dom.spec.whatwg.org/#dom-parentnode-queryselector + fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> { + // 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) { + let elem: &JSRef<Element> = 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<Temporary<NodeList>> { + // 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( + |node| node.is_element() && matches(selectors, node)).collect() + } + } + let window = window_from_node(self).root(); + Ok(NodeList::new_simple_list(&window.root_ref(), nodes)) + } + + fn ancestors(&self) -> AncestorIterator<'n> { + AncestorIterator { + current: self.parent_node.get().map(|node| (*node.root()).clone()), + } + } + + fn owner_doc(&self) -> Temporary<Document> { + Temporary::new(self.owner_doc.get().get_ref().clone()) + } + + fn set_owner_doc(&self, document: &JSRef<Document>) { + 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<'n> { + AbstractNodeChildrenIterator { + current_node: self.first_child.get().map(|node| (*node.root()).clone()), + } + } + + fn child_elements(&self) -> ChildElementIterator<'m, 'n> { + self.children() + .filter(|node| { + node.is_element() + }) + .map(|node| { + let elem: &JSRef<Element> = ElementCast::to_ref(&node).unwrap(); + elem.clone() + }) + } + + fn wait_until_safe_to_modify_dom(&self) { + let document = self.owner_doc().root(); + document.deref().wait_until_safe_to_modify_dom(); + } + + fn remove_self(&self) { + match self.parent_node().root() { + Some(ref parent) => parent.remove_child(self), + None => () + } + } +} + +/// 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<Node> { + 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<Node>` 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<JS<Node>>; + unsafe fn first_child_ref(&self) -> Option<JS<Node>>; + unsafe fn last_child_ref(&self) -> Option<JS<Node>>; + unsafe fn prev_sibling_ref(&self) -> Option<JS<Node>>; + unsafe fn next_sibling_ref(&self) -> Option<JS<Node>>; + + unsafe fn owner_doc_for_layout(&self) -> JS<Document>; + + unsafe fn is_element_for_layout(&self) -> bool; +} + +impl LayoutNodeHelpers for JS<Node> { + #[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<JS<Node>> { + (*self.unsafe_get()).parent_node.get() + } + + #[inline] + unsafe fn first_child_ref(&self) -> Option<JS<Node>> { + (*self.unsafe_get()).first_child.get() + } + + #[inline] + unsafe fn last_child_ref(&self) -> Option<JS<Node>> { + (*self.unsafe_get()).last_child.get() + } + + #[inline] + unsafe fn prev_sibling_ref(&self) -> Option<JS<Node>> { + (*self.unsafe_get()).prev_sibling.get() + } + + #[inline] + unsafe fn next_sibling_ref(&self) -> Option<JS<Node>> { + (*self.unsafe_get()).next_sibling.get() + } + + #[inline] + unsafe fn owner_doc_for_layout(&self) -> JS<Document> { + (*self.unsafe_get()).owner_doc.get().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, 'b> = Map<'a, JSRef<'b, Node>, + JSRef<'b, Element>, + Filter<'a, JSRef<'b, Node>, AbstractNodeChildrenIterator<'b>>>; + +pub struct AbstractNodeChildrenIterator<'a> { + current_node: Option<JSRef<'a, Node>>, +} + +impl<'a> Iterator<JSRef<'a, Node>> for AbstractNodeChildrenIterator<'a> { + fn next(&mut self) -> Option<JSRef<'a, Node>> { + 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<JSRef<'a, Node>>, +} + +impl<'a> Iterator<JSRef<'a, Node>> for AncestorIterator<'a> { + fn next(&mut self) -> Option<JSRef<'a, Node>> { + if self.current.is_none() { + return None; + } + + // FIXME: Do we need two clones here? + let x = self.current.get_ref().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<JSRef<'a, Node>>, + index: uint, +} + +impl<'a> TreeIterator<'a> { + fn new(nodes: Vec<JSRef<'a, Node>>) -> TreeIterator<'a> { + TreeIterator { + nodes: nodes, + index: 0, + } + } +} + +impl<'a> Iterator<JSRef<'a, Node>> for TreeIterator<'a> { + fn next(&mut self) -> Option<JSRef<'a, Node>> { + if self.index >= self.nodes.len() { + None + } else { + let v = self.nodes[self.index]; + let v = v.clone(); + self.index += 1; + Some(v) + } + } +} + +pub struct NodeIterator { + pub start_node: JS<Node>, + pub current_node: Option<JS<Node>>, + 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<JSRef<'b, Node>> { + if !self.include_descendants_of_void && node.is_element() { + let elem: &JSRef<Element> = ElementCast::to_ref(node).unwrap(); + if elem.deref().is_void() { + None + } else { + node.first_child().map(|child| (*child.root()).clone()) + } + } else { + node.first_child().map(|child| (*child.root()).clone()) + } + } +} + +impl<'a> Iterator<JSRef<'a, Node>> for NodeIterator { + fn next(&mut self) -> Option<JSRef<'a, Node>> { + 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.deref().next_sibling().root() { + Some(sibling) => Some(JS::from_rooted(&*sibling)), + None => { + let mut candidate = node.deref().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().deref())) + } else { + None + } + } + } + } + } + } + }; + self.current_node.map(|node| (*node.root()).clone()) + } +} + +fn gather_abstract_nodes<'a>(cur: &JSRef<'a, Node>, refs: &mut Vec<JSRef<'a, Node>>, 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: &T) -> uintptr_t { t as *const T as uintptr_t } + +impl Node { + pub fn reflect_node<N: Reflectable+NodeBase> + (node: Box<N>, + document: &JSRef<Document>, + wrap_fn: extern "Rust" fn(*mut JSContext, &GlobalRef, Box<N>) -> Temporary<N>) + -> Temporary<N> { + let window = document.window.root(); + reflect_dom_object(node, &Window(*window), wrap_fn) + } + + pub fn new_inherited(type_id: NodeTypeId, doc: &JSRef<Document>) -> 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<JSRef<Document>>) -> Node { + Node { + eventtarget: EventTarget::new_inherited(NodeTargetTypeId(type_id)), + type_id: type_id, + + parent_node: Cell::new(None), + first_child: Cell::new(None), + last_child: Cell::new(None), + next_sibling: Cell::new(None), + prev_sibling: Cell::new(None), + owner_doc: Cell::new(doc.unrooted()), + child_list: Cell::new(None), + + flags: Traceable::new(RefCell::new(NodeFlags::new(type_id))), + + layout_data: LayoutDataRef::new(), + } + } + + // http://dom.spec.whatwg.org/#concept-node-adopt + pub fn adopt(node: &JSRef<Node>, document: &JSRef<Document>) { + // 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<Node>, parent: &JSRef<Node>, child: Option<JSRef<Node>>) + -> Fallible<Temporary<Node>> { + // 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(ref 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(ref 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(ref 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<Node>, + parent: &JSRef<Node>, + child: Option<JSRef<Node>>, + 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.mut_iter() { + 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.deref().borrow_mut().insert(IsInDoc); + } else { + kid.flags.deref().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<JSRef<Node>>, parent: &JSRef<Node>) { + + // Step 1. + match node { + Some(ref node) => { + let document = document_from_node(parent).root(); + Node::adopt(node, &*document); + } + None => (), + } + + // Step 2. + let removedNodes: Vec<JSRef<Node>> = parent.children().collect(); + + // Step 3. + let addedNodes = match node { + None => vec!(), + Some(ref 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(ref 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<Node>, parent: &JSRef<Node>) -> Fallible<Temporary<Node>> { + // 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<Node>, parent: &JSRef<Node>, 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.deref().flags.deref().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<Node>, maybe_doc: Option<&JSRef<Document>>, + clone_children: CloneChildrenFlag) -> Temporary<Node> { + + // 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<Node> = match node.type_id() { + DoctypeNodeTypeId => { + let doctype: &JSRef<DocumentType> = DocumentTypeCast::to_ref(node).unwrap(); + let doctype = doctype.deref(); + 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<Comment> = CommentCast::to_ref(node).unwrap(); + let comment = comment.deref(); + let comment = Comment::new(comment.characterdata.data.deref().borrow().clone(), &*document); + NodeCast::from_temporary(comment) + }, + DocumentNodeTypeId => { + let document: &JSRef<Document> = 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<Element> = ElementCast::to_ref(node).unwrap(); + let element = element.deref(); + let element = build_element_from_tag(element.local_name.as_slice().to_string(), + element.namespace.clone(), &*document); + NodeCast::from_temporary(element) + }, + TextNodeTypeId => { + let text: &JSRef<Text> = TextCast::to_ref(node).unwrap(); + let text = text.deref(); + let text = Text::new(text.characterdata.data.deref().borrow().clone(), &*document); + NodeCast::from_temporary(text) + }, + ProcessingInstructionNodeTypeId => { + let pi: &JSRef<ProcessingInstruction> = ProcessingInstructionCast::to_ref(node).unwrap(); + let pi = pi.deref(); + let pi = ProcessingInstruction::new(pi.target.clone(), + pi.characterdata.data.deref().borrow().clone(), &*document); + NodeCast::from_temporary(pi) + }, + }.root(); + + // Step 3. + let document = if copy.is_document() { + let doc: &JSRef<Document> = 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<Document> = DocumentCast::to_ref(node).unwrap(); + let copy_doc: &JSRef<Document> = DocumentCast::to_ref(&*copy).unwrap(); + copy_doc.set_encoding_name(node_doc.encoding_name.deref().borrow().clone()); + copy_doc.set_quirks_mode(node_doc.quirks_mode()); + }, + ElementNodeTypeId(..) => { + let node_elem: &JSRef<Element> = ElementCast::to_ref(node).unwrap(); + let copy_elem: &JSRef<Element> = ElementCast::to_ref(&*copy).unwrap(); + + // FIXME: https://github.com/mozilla/servo/issues/1737 + let window = document.deref().window.root(); + for attr in node_elem.deref().attrs.borrow().iter().map(|attr| attr.root()) { + copy_elem.deref().attrs.borrow_mut().push_unrooted( + &Attr::new(&*window, + attr.local_name().clone(), attr.deref().value().clone(), + attr.deref().name.clone(), attr.deref().namespace.clone(), + attr.deref().prefix.clone(), copy_elem)); + } + }, + _ => () + } + + // Step 5: cloning steps. + + // Step 6. + if clone_children == CloneChildren { + for ref 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<JSRef<'a, Node>>>(mut iterator: T) -> String { + let mut content = String::new(); + for node in iterator { + let text: Option<&JSRef<Text>> = 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<Element> = ElementCast::to_ref(self).unwrap(); + elem.TagName() + } + TextNodeTypeId => "#text".to_string(), + ProcessingInstructionNodeTypeId => { + let processing_instruction: &JSRef<ProcessingInstruction> = + ProcessingInstructionCast::to_ref(self).unwrap(); + processing_instruction.Target() + } + CommentNodeTypeId => "#comment".to_string(), + DoctypeNodeTypeId => { + let doctype: &JSRef<DocumentType> = DocumentTypeCast::to_ref(self).unwrap(); + doctype.deref().name.clone() + }, + DocumentFragmentNodeTypeId => "#document-fragment".to_string(), + DocumentNodeTypeId => "#document".to_string() + } + } + + // http://dom.spec.whatwg.org/#dom-node-baseuri + fn GetBaseURI(&self) -> Option<DOMString> { + // FIXME (#1824) implement. + None + } + + // http://dom.spec.whatwg.org/#dom-node-ownerdocument + fn GetOwnerDocument(&self) -> Option<Temporary<Document>> { + 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<Temporary<Node>> { + self.parent_node.get().map(|node| Temporary::new(node)) + } + + // http://dom.spec.whatwg.org/#dom-node-parentelement + fn GetParentElement(&self) -> Option<Temporary<Element>> { + 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<NodeList> { + match self.child_list.get() { + None => (), + Some(ref list) => return Temporary::new(list.clone()), + } + + let doc = self.owner_doc().root(); + let window = doc.deref().window.root(); + let child_list = NodeList::new_child_list(&*window, self); + self.child_list.assign(Some(child_list)); + Temporary::new(self.child_list.get().get_ref().clone()) + } + + // http://dom.spec.whatwg.org/#dom-node-firstchild + fn GetFirstChild(&self) -> Option<Temporary<Node>> { + self.first_child.get().map(|node| Temporary::new(node)) + } + + // http://dom.spec.whatwg.org/#dom-node-lastchild + fn GetLastChild(&self) -> Option<Temporary<Node>> { + self.last_child.get().map(|node| Temporary::new(node)) + } + + // http://dom.spec.whatwg.org/#dom-node-previoussibling + fn GetPreviousSibling(&self) -> Option<Temporary<Node>> { + self.prev_sibling.get().map(|node| Temporary::new(node)) + } + + // http://dom.spec.whatwg.org/#dom-node-nextsibling + fn GetNextSibling(&self) -> Option<Temporary<Node>> { + self.next_sibling.get().map(|node| Temporary::new(node)) + } + + // http://dom.spec.whatwg.org/#dom-node-nodevalue + fn GetNodeValue(&self) -> Option<DOMString> { + match self.type_id { + CommentNodeTypeId | + TextNodeTypeId | + ProcessingInstructionNodeTypeId => { + let chardata: &JSRef<CharacterData> = CharacterDataCast::to_ref(self).unwrap(); + Some(chardata.Data()) + } + _ => { + None + } + } + } + + // http://dom.spec.whatwg.org/#dom-node-nodevalue + fn SetNodeValue(&self, val: Option<DOMString>) { + match self.type_id { + CommentNodeTypeId | + TextNodeTypeId | + ProcessingInstructionNodeTypeId => { + self.SetTextContent(val) + } + _ => {} + } + } + + // http://dom.spec.whatwg.org/#dom-node-textcontent + fn GetTextContent(&self) -> Option<DOMString> { + match self.type_id { + DocumentFragmentNodeTypeId | + ElementNodeTypeId(..) => { + let content = Node::collect_text_contents(self.traverse_preorder()); + Some(content) + } + CommentNodeTypeId | + TextNodeTypeId | + ProcessingInstructionNodeTypeId => { + let characterdata: &JSRef<CharacterData> = CharacterDataCast::to_ref(self).unwrap(); + Some(characterdata.Data()) + } + DoctypeNodeTypeId | + DocumentNodeTypeId => { + None + } + } + } + + // http://dom.spec.whatwg.org/#dom-node-textcontent + fn SetTextContent(&self, value: Option<DOMString>) { + 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.deref().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<CharacterData> = CharacterDataCast::to_ref(self).unwrap(); + *characterdata.data.deref().borrow_mut() = value; + + // Notify the document that the content of this node is different + let document = self.owner_doc().root(); + document.deref().content_changed(); + } + DoctypeNodeTypeId | + DocumentNodeTypeId => {} + } + } + + // http://dom.spec.whatwg.org/#dom-node-insertbefore + fn InsertBefore(&self, node: &JSRef<Node>, child: Option<JSRef<Node>>) -> Fallible<Temporary<Node>> { + Node::pre_insert(node, self, child) + } + + // http://dom.spec.whatwg.org/#dom-node-appendchild + fn AppendChild(&self, node: &JSRef<Node>) -> Fallible<Temporary<Node>> { + Node::pre_insert(node, self, None) + } + + // http://dom.spec.whatwg.org/#concept-node-replace + fn ReplaceChild(&self, node: &JSRef<Node>, child: &JSRef<Node>) -> Fallible<Temporary<Node>> { + + // 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(ref 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<Node>) + -> Fallible<Temporary<Node>> { + 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<CharacterData> = CharacterDataCast::to_ref(&child).unwrap(); + if characterdata.Length() == 0 { + self.remove_child(&child); + } else { + match prev_text { + Some(ref mut text_node) => { + let prev_characterdata: &mut JSRef<CharacterData> = CharacterDataCast::to_mut_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<Node> { + 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<JSRef<Node>>) -> bool { + fn is_equal_doctype(node: &JSRef<Node>, other: &JSRef<Node>) -> bool { + let doctype: &JSRef<DocumentType> = DocumentTypeCast::to_ref(node).unwrap(); + let other_doctype: &JSRef<DocumentType> = DocumentTypeCast::to_ref(other).unwrap(); + (doctype.deref().name == other_doctype.deref().name) && + (doctype.deref().public_id == other_doctype.deref().public_id) && + (doctype.deref().system_id == other_doctype.deref().system_id) + } + fn is_equal_element(node: &JSRef<Node>, other: &JSRef<Node>) -> bool { + let element: &JSRef<Element> = ElementCast::to_ref(node).unwrap(); + let other_element: &JSRef<Element> = ElementCast::to_ref(other).unwrap(); + // FIXME: namespace prefix + let element = element.deref(); + let other_element = other_element.deref(); + (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<Node>, other: &JSRef<Node>) -> bool { + let pi: &JSRef<ProcessingInstruction> = ProcessingInstructionCast::to_ref(node).unwrap(); + let other_pi: &JSRef<ProcessingInstruction> = ProcessingInstructionCast::to_ref(other).unwrap(); + (pi.deref().target == other_pi.deref().target) && + (*pi.deref().characterdata.data.deref().borrow() == *other_pi.deref().characterdata.data.deref().borrow()) + } + fn is_equal_characterdata(node: &JSRef<Node>, other: &JSRef<Node>) -> bool { + let characterdata: &JSRef<CharacterData> = CharacterDataCast::to_ref(node).unwrap(); + let other_characterdata: &JSRef<CharacterData> = CharacterDataCast::to_ref(other).unwrap(); + *characterdata.deref().data.deref().borrow() == *other_characterdata.deref().data.deref().borrow() + } + fn is_equal_element_attrs(node: &JSRef<Node>, other: &JSRef<Node>) -> bool { + let element: &JSRef<Element> = ElementCast::to_ref(node).unwrap(); + let other_element: &JSRef<Element> = ElementCast::to_ref(other).unwrap(); + let element = element.deref(); + let other_element = other_element.deref(); + 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.deref().value().as_slice() == other_attr.deref().value().as_slice()) + }) + }) + } + fn is_equal_node(this: &JSRef<Node>, node: &JSRef<Node>) -> 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(ref node) => is_equal_node(self, node) + } + } + + // http://dom.spec.whatwg.org/#dom-node-comparedocumentposition + fn CompareDocumentPosition(&self, other: &JSRef<Node>) -> 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<JSRef<Node>>) -> bool { + match maybe_other { + None => false, + Some(ref other) => self.is_inclusive_ancestor_of(other) + } + } + + // http://dom.spec.whatwg.org/#dom-node-lookupprefix + fn LookupPrefix(&self, _prefix: Option<DOMString>) -> Option<DOMString> { + // FIXME (#1826) implement. + None + } + + // http://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri + fn LookupNamespaceURI(&self, _namespace: Option<DOMString>) -> Option<DOMString> { + // FIXME (#1826) implement. + None + } + + // http://dom.spec.whatwg.org/#dom-node-isdefaultnamespace + fn IsDefaultNamespace(&self, _namespace: Option<DOMString>) -> bool { + // FIXME (#1826) implement. + false + } +} + + +impl Reflectable for Node { + fn reflector<'a>(&'a self) -> &'a Reflector { + self.eventtarget.reflector() + } +} + +pub fn document_from_node<T: NodeBase>(derived: &JSRef<T>) -> Temporary<Document> { + let node: &JSRef<Node> = NodeCast::from_ref(derived); + node.owner_doc() +} + +pub fn window_from_node<T: NodeBase>(derived: &JSRef<T>) -> Temporary<Window> { + let document = document_from_node(derived).root(); + Temporary::new(document.deref().window.clone()) +} + +impl<'a> VirtualMethods for JSRef<'a, Node> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self); + Some(eventtarget as &VirtualMethods) + } +} + +impl<'a> style::TNode<JSRef<'a, Element>> for JSRef<'a, Node> { + fn parent_node(&self) -> Option<JSRef<'a, Node>> { + (self as &NodeHelpers).parent_node().map(|node| *node.root()) + } + + fn prev_sibling(&self) -> Option<JSRef<'a, Node>> { + (self as &NodeHelpers).prev_sibling().map(|node| *node.root()) + } + + fn next_sibling(&self) -> Option<JSRef<'a, Node>> { + (self as &NodeHelpers).next_sibling().map(|node| *node.root()) + } + + fn is_document(&self) -> bool { + (self as &NodeHelpers).is_document() + } + + fn is_element(&self) -> bool { + (self as &NodeHelpers).is_element() + } + + fn as_element(&self) -> JSRef<'a, Element> { + let elem: Option<&JSRef<'a, Element>> = 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.deref().Value().as_slice())) + }, + // FIXME: https://github.com/mozilla/servo/issues/1558 + style::AnyNamespace => false, + } + } + + fn is_html_element_in_html_document(&self) -> bool { + let elem: Option<&JSRef<'a, Element>> = ElementCast::to_ref(self); + assert!(elem.is_some()); + let elem: &ElementHelpers = elem.unwrap() as &ElementHelpers; + elem.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(ref 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); + } +} |