/* 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>>,
/// The first child of this node.
first_child: Cell >>,
/// The last child of this node.
last_child: Cell >>,
/// The next sibling of this node.
next_sibling: Cell >>,
/// The previous sibling of this node.
prev_sibling: Cell >>,
/// The document that this node belongs to.
owner_doc: Cell >>,
/// The live list of children return by .childNodes.
child_list: Cell >>,
/// A bitfield of flags for node items.
flags: Traceable>,
/// 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, E> Encodable 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>,
}
/// Encapsulates the abstract layout data.
pub struct LayoutData {
chan: Option,
_shared_data: SharedLayoutData,
_data: *const (),
}
pub struct LayoutDataRef {
pub data_cell: RefCell>,
}
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 *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 {
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,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, 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.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, before: Option>) {
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) {
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) -> 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<'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;
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);
}
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> {
self.deref().parent_node.get().map(|node| Temporary::new(node))
}
fn first_child(&self) -> Option> {
self.deref().first_child.get().map(|node| Temporary::new(node))
}
fn last_child(&self) -> Option> {
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> {
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> {
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) -> 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) -> 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.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> {
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>> {
// 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 = 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(
|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 {
Temporary::new(self.owner_doc.get().get_ref().clone())
}
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<'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 = 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 {
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()
}
#[inline]
unsafe fn first_child_ref(&self) -> Option> {
(*self.unsafe_get()).first_child.get()
}
#[inline]
unsafe fn last_child_ref(&self) -> Option> {
(*self.unsafe_get()).last_child.get()
}
#[inline]
unsafe fn prev_sibling_ref(&self) -> Option> {
(*self.unsafe_get()).prev_sibling.get()
}
#[inline]
unsafe fn next_sibling_ref(&self) -> Option> {
(*self.unsafe_get()).next_sibling.get()
}
#[inline]
unsafe fn owner_doc_for_layout(&self) -> JS {
(*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>,
}
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.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>,
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)
}
}
}
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.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> 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.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>, 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, &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: 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, 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(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,
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.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>, parent: &JSRef) {
// Step 1.
match node {
Some(ref 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(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, 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.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, maybe_doc: Option<&JSRef>,
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 = 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 = 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 = 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 = 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 = 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 = 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 = 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.deref().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.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>>(mut iterator: T) -> String {
let mut content = String::new();
for node in iterator {
let text: Option<&JSRef> = 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.deref().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().map(|node| Temporary::new(node))
}
// 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(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> {
self.first_child.get().map(|node| Temporary::new(node))
}
// http://dom.spec.whatwg.org/#dom-node-lastchild
fn GetLastChild(&self) -> Option> {
self.last_child.get().map(|node| Temporary::new(node))
}
// http://dom.spec.whatwg.org/#dom-node-previoussibling
fn GetPreviousSibling(&self) -> Option> {
self.prev_sibling.get().map(|node| Temporary::new(node))
}
// http://dom.spec.whatwg.org/#dom-node-nextsibling
fn GetNextSibling(&self) -> Option> {
self.next_sibling.get().map(|node| Temporary::new(node))
}
// 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.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 = 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, 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(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)
-> 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(ref mut text_node) => {
let prev_characterdata: &mut JSRef = 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 {
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.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, other: &JSRef) -> bool {
let element: &JSRef = ElementCast::to_ref(node).unwrap();
let other_element: &JSRef = 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, other: &JSRef) -> bool {
let pi: &JSRef = ProcessingInstructionCast::to_ref(node).unwrap();
let other_pi: &JSRef = 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, other: &JSRef) -> bool {
let characterdata: &JSRef = CharacterDataCast::to_ref(node).unwrap();
let other_characterdata: &JSRef = CharacterDataCast::to_ref(other).unwrap();
*characterdata.deref().data.deref().borrow() == *other_characterdata.deref().data.deref().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();
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: &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(ref 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(ref 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.deref().window.clone())
}
impl<'a> VirtualMethods for JSRef<'a, Node> {
fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
let eventtarget: &JSRef = EventTargetCast::from_ref(self);
Some(eventtarget as &VirtualMethods)
}
}
impl<'a> style::TNode> for JSRef<'a, Node> {
fn parent_node(&self) -> Option> {
(self as &NodeHelpers).parent_node().map(|node| *node.root())
}
fn prev_sibling(&self) -> Option> {
(self as &NodeHelpers).prev_sibling().map(|node| *node.root())
}
fn next_sibling(&self) -> Option> {
(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);
}
}