/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements.

use dom::attr::{Attr, AttrHelpers};
use dom::bindings::cell::DOMRefCell;
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::NamedNodeMapBinding::NamedNodeMapMethods;
use dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods;
use dom::bindings::codegen::InheritTypes::{CharacterDataCast, DocumentCast, DocumentTypeCast};
use dom::bindings::codegen::InheritTypes::{ElementCast, NodeCast, ElementDerived, EventTargetCast};
use dom::bindings::codegen::InheritTypes::{HTMLLegendElementDerived, HTMLFieldSetElementDerived};
use dom::bindings::codegen::InheritTypes::{HTMLOptGroupElementDerived, NodeBase, NodeDerived};
use dom::bindings::codegen::InheritTypes::{ProcessingInstructionCast, TextCast};
use dom::bindings::codegen::UnionTypes::NodeOrString;
use dom::bindings::conversions;
use dom::bindings::error::{ErrorResult, Fallible};
use dom::bindings::error::Error::{NotFound, HierarchyRequest, Syntax};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, JSRef, LayoutJS, MutNullableHeap};
use dom::bindings::js::{OptionalRootable, ResultRootable, Root, Rootable};
use dom::bindings::js::{RootedReference, Temporary, TemporaryPushable};
use dom::bindings::js::Unrooted;
use dom::bindings::trace::JSTraceable;
use dom::bindings::trace::RootedVec;
use dom::bindings::utils::{Reflectable, reflect_dom_object};
use dom::characterdata::{CharacterData, CharacterDataHelpers, CharacterDataTypeId};
use dom::comment::Comment;
use dom::document::{Document, DocumentHelpers, IsHTMLDocument, DocumentSource};
use dom::documentfragment::DocumentFragment;
use dom::documenttype::DocumentType;
use dom::element::{AttributeHandlers, Element, ElementCreator, ElementTypeId};
use dom::element::ElementHelpers;
use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::HTMLElementTypeId;
use dom::nodelist::NodeList;
use dom::processinginstruction::{ProcessingInstruction, ProcessingInstructionHelpers};
use dom::text::Text;
use dom::virtualmethods::{VirtualMethods, vtable_for};
use dom::window::{Window, WindowHelpers};
use geom::rect::Rect;
use layout_interface::{LayoutChan, Msg};
use devtools_traits::NodeInfo;
use parse::html::parse_html_fragment;
use script_traits::UntrustedNodeAddress;
use util::geometry::Au;
use util::str::{DOMString, null_str_as_empty};
use selectors::parser::{Selector, AttrSelector, NamespaceConstraint};
use selectors::parser::parse_author_origin_selector_list_from_str;
use selectors::matching::matches;
use style::properties::ComputedValues;
use style;

use js::jsapi::{JSContext, JSObject, JSRuntime};
use js::jsfriendapi;
use core::nonzero::NonZero;
use libc;
use libc::{uintptr_t, c_void};
use std::borrow::ToOwned;
use std::cell::{Cell, RefCell, Ref, RefMut};
use std::default::Default;
use std::iter::{FilterMap, Peekable};
use std::mem;
use std::sync::Arc;
use uuid;
use string_cache::{Atom, QualName};

//
// The basic Node structure
//

/// An HTML node.
#[dom_struct]
pub struct Node {
    /// The JavaScript reflector for this node.
    eventtarget: EventTarget,

    /// The type of node that this is.
    type_id: NodeTypeId,

    /// The parent of this node.
    parent_node: MutNullableHeap<JS<Node>>,

    /// The first child of this node.
    first_child: MutNullableHeap<JS<Node>>,

    /// The last child of this node.
    last_child: MutNullableHeap<JS<Node>>,

    /// The next sibling of this node.
    next_sibling: MutNullableHeap<JS<Node>>,

    /// The previous sibling of this node.
    prev_sibling: MutNullableHeap<JS<Node>>,

    /// The document that this node belongs to.
    owner_doc: MutNullableHeap<JS<Document>>,

    /// The live list of children return by .childNodes.
    child_list: MutNullableHeap<JS<NodeList>>,

    /// A bitfield of flags for node items.
    flags: Cell<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.
    layout_data: LayoutDataRef,

    unique_id: DOMRefCell<String>,
}

impl NodeDerived for EventTarget {
    fn is_node(&self) -> bool {
        match *self.type_id() {
            EventTargetTypeId::Node(_) => true,
            _ => false
        }
    }
}

bitflags! {
    #[doc = "Flags for node items."]
    #[jstraceable]
    flags NodeFlags: u16 {
        #[doc = "Specifies whether this node is in a document."]
        const IS_IN_DOC = 0x01,
        #[doc = "Specifies whether this node is in hover state."]
        const IN_HOVER_STATE = 0x02,
        #[doc = "Specifies whether this node is in disabled state."]
        const IN_DISABLED_STATE = 0x04,
        #[doc = "Specifies whether this node is in enabled state."]
        const IN_ENABLED_STATE = 0x08,
        #[doc = "Specifies whether this node _must_ be reflowed regardless of style differences."]
        const HAS_CHANGED = 0x10,
        #[doc = "Specifies whether this node needs style recalc on next reflow."]
        const IS_DIRTY = 0x20,
        #[doc = "Specifies whether this node has siblings (inclusive of itself) which \
                  changed since the last reflow."]
        const HAS_DIRTY_SIBLINGS = 0x40,
        #[doc = "Specifies whether this node has descendants (inclusive of itself) which \
                 have changed since the last reflow."]
        const HAS_DIRTY_DESCENDANTS = 0x80,
        // TODO: find a better place to keep this (#4105)
        // https://critic.hoppipolla.co.uk/showcomment?chain=8873
        // Perhaps using a Set in Document?
        #[doc = "Specifies whether or not there is an authentic click in progress on \
                 this element."]
        const CLICK_IN_PROGRESS = 0x100,
        #[doc = "Specifies whether this node has the focus."]
        const IN_FOCUS_STATE = 0x200,
    }
}

impl NodeFlags {
    pub fn new(type_id: NodeTypeId) -> NodeFlags {
        let dirty = HAS_CHANGED | IS_DIRTY | HAS_DIRTY_SIBLINGS | HAS_DIRTY_DESCENDANTS;
        match type_id {
            NodeTypeId::Document => IS_IN_DOC | dirty,
            // The following elements are enabled by default.
            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) |
            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) |
            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) |
            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) |
            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptGroupElement)) |
            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptionElement)) |
            //NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLMenuItemElement)) |
            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFieldSetElement)) => IN_ENABLED_STATE | dirty,
            _ => dirty,
        }
    }
}

impl Drop for Node {
    #[allow(unsafe_code)]
    fn drop(&mut self) {
        self.layout_data.dispose();
    }
}

/// suppress observers flag
/// https://dom.spec.whatwg.org/#concept-node-insert
/// https://dom.spec.whatwg.org/#concept-node-remove
#[derive(Copy, Clone)]
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: NonZero<*const ()>,
}

#[allow(unsafe_code)]
unsafe impl Send for LayoutData {}

pub struct LayoutDataRef {
    pub data_cell: RefCell<Option<LayoutData>>,
}

no_jsmanaged_fields!(LayoutDataRef);

impl LayoutDataRef {
    pub fn new() -> LayoutDataRef {
        LayoutDataRef {
            data_cell: RefCell::new(None),
        }
    }

    /// Sends layout data, if any, back to the layout task to be destroyed.
    pub fn dispose(&self) {
        if let Some(mut layout_data) = mem::replace(&mut *self.borrow_mut(), None) {
            let layout_chan = layout_data.chan.take();
            match layout_chan {
                None => {}
                Some(chan) => {
                    let LayoutChan(chan) = chan;
                    chan.send(Msg::ReapLayoutData(layout_data)).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]
    #[allow(unsafe_code)]
    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.
#[derive(Copy, Clone, PartialEq, Debug)]
#[jstraceable]
pub enum NodeTypeId {
    CharacterData(CharacterDataTypeId),
    DocumentType,
    DocumentFragment,
    Document,
    Element(ElementTypeId),
}

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> {
    // https://dom.spec.whatwg.org/#node-is-inserted
    fn node_inserted(self) {
        assert!(self.parent_node.get().is_some());
        let document = document_from_node(self).root();
        let is_in_doc = self.is_in_doc();

        for node in self.traverse_preorder() {
            let node = node.root();
            vtable_for(&node.r()).bind_to_tree(is_in_doc);
        }

        let parent = self.parent_node.get().root();
        parent.r().map(|parent| vtable_for(&parent).child_inserted(self));
        document.r().content_and_heritage_changed(self, NodeDamage::OtherNodeDamage);
    }

    // https://dom.spec.whatwg.org/#node-is-removed
    fn node_removed(self, parent_in_doc: bool) {
        assert!(self.parent_node.get().is_none());
        for node in self.traverse_preorder() {
            let node = node.root();
            vtable_for(&node.r()).unbind_from_tree(parent_in_doc);
        }
        self.layout_data.dispose();
    }

    //
    // 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>>) {
        assert!(new_child.parent_node.get().is_none());
        assert!(new_child.prev_sibling.get().is_none());
        assert!(new_child.next_sibling.get().is_none());
        match before {
            Some(ref before) => {
                assert!(before.parent_node.get().root().r() == Some(self));
                match before.prev_sibling.get().root() {
                    None => {
                        assert!(Some(*before) == self.first_child.get().root().r());
                        self.first_child.set(Some(JS::from_rooted(new_child)));
                    },
                    Some(ref prev_sibling) => {
                        prev_sibling.r().next_sibling.set(Some(JS::from_rooted(new_child)));
                        new_child.prev_sibling.set(Some(JS::from_rooted(prev_sibling.r())));
                    },
                }
                before.prev_sibling.set(Some(JS::from_rooted(new_child)));
                new_child.next_sibling.set(Some(JS::from_rooted(*before)));
            },
            None => {
                match self.last_child.get().root() {
                    None => self.first_child.set(Some(JS::from_rooted(new_child))),
                    Some(ref last_child) => {
                        assert!(last_child.r().next_sibling.get().is_none());
                        last_child.r().next_sibling.set(Some(JS::from_rooted(new_child)));
                        new_child.prev_sibling.set(Some(JS::from_rooted(last_child.r())));
                    }
                }

                self.last_child.set(Some(JS::from_rooted(new_child)));
            },
        }

        new_child.parent_node.set(Some(JS::from_rooted(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>) {
        assert!(child.parent_node.get().root().r() == Some(self));

        match child.prev_sibling.get().root() {
            None => {
                self.first_child.set(child.next_sibling.get());
            }
            Some(ref prev_sibling) => {
                prev_sibling.r().next_sibling.set(child.next_sibling.get());
            }
        }

        match child.next_sibling.get().root() {
            None => {
                self.last_child.set(child.prev_sibling.get());
            }
            Some(ref next_sibling) => {
                next_sibling.r().prev_sibling.set(child.prev_sibling.get());
            }
        }

        child.prev_sibling.set(None);
        child.next_sibling.set(None);
        child.parent_node.set(None);
    }
}

pub struct QuerySelectorIterator {
    selectors: Vec<Selector>,
    iterator: TreeIterator,
}

impl<'a> QuerySelectorIterator {
    #[allow(unsafe_code)]
    unsafe fn new(iter: TreeIterator, selectors: Vec<Selector>)
                  -> QuerySelectorIterator {
        QuerySelectorIterator {
            selectors: selectors,
            iterator: iter,
        }
    }
}

impl<'a> Iterator for QuerySelectorIterator {
    type Item = Temporary<Node>;

    fn next(&mut self) -> Option<Temporary<Node>> {
        let selectors = &self.selectors;
        // TODO(cgaebel): Is it worth it to build a bloom filter here
        // (instead of passing `None`)? Probably.
        self.iterator.find(|node| {
            let node = node.root();
            node.r().is_element() && matches(selectors, &node.r(), &mut None)
        })
    }
}

pub trait NodeHelpers {
    fn ancestors(self) -> AncestorIterator;
    fn inclusive_ancestors(self) -> AncestorIterator;
    fn children(self) -> NodeSiblingIterator;
    fn rev_children(self) -> ReverseSiblingIterator;
    fn child_elements(self) -> ChildElementIterator;
    fn following_siblings(self) -> NodeSiblingIterator;
    fn preceding_siblings(self) -> ReverseSiblingIterator;
    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 len(self) -> u32;
    fn index(self) -> u32;

    fn owner_doc(self) -> Temporary<Document>;
    fn set_owner_doc(self, document: JSRef<Document>);
    fn is_in_html_doc(self) -> bool;

    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_flag(self, flag: NodeFlags) -> bool;
    fn set_flag(self, flag: NodeFlags, value: bool);

    fn get_hover_state(self) -> bool;
    fn set_hover_state(self, state: bool);

    fn get_focus_state(self) -> bool;
    fn set_focus_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 get_has_changed(self) -> bool;
    fn set_has_changed(self, state: bool);

    fn get_is_dirty(self) -> bool;
    fn set_is_dirty(self, state: bool);

    fn get_has_dirty_siblings(self) -> bool;
    fn set_has_dirty_siblings(self, state: bool);

    fn get_has_dirty_descendants(self) -> bool;
    fn set_has_dirty_descendants(self, state: bool);

    /// Marks the given node as `IS_DIRTY`, its siblings as `HAS_DIRTY_SIBLINGS` (to deal with
    /// sibling selectors), its ancestors as `HAS_DIRTY_DESCENDANTS`, and its descendants as
    /// `IS_DIRTY`. If anything more than the node's style was damaged, this method also sets the
    /// `HAS_CHANGED` flag.
    fn dirty(self, damage: NodeDamage);

    /// Similar to `dirty`, but will always walk the ancestors to mark them dirty,
    /// too. This is useful when a node is reparented. The node will frequently
    /// already be marked as `changed` to skip double-dirties, but the ancestors
    /// still need to be marked as `HAS_DIRTY_DESCENDANTS`.
    ///
    /// See #4170
    fn force_dirty_ancestors(self, damage: NodeDamage);

    fn dirty_impl(self, damage: NodeDamage, force_ancestors: bool);

    fn dump(self);
    fn dump_indent(self, indent: u32);
    fn debug_str(self) -> String;

    fn traverse_preorder(self) -> TreeIterator;
    fn inclusively_following_siblings(self) -> NodeSiblingIterator;
    fn inclusively_preceding_siblings(self) -> ReverseSiblingIterator;

    fn to_trusted_node_address(self) -> TrustedNodeAddress;

    fn get_bounding_content_box(self) -> Rect<Au>;
    fn get_content_boxes(self) -> Vec<Rect<Au>>;

    fn before(self, nodes: Vec<NodeOrString>) -> ErrorResult;
    fn after(self, nodes: Vec<NodeOrString>) -> ErrorResult;
    fn replace_with(self, nodes: Vec<NodeOrString>) -> ErrorResult;
    fn prepend(self, nodes: Vec<NodeOrString>) -> ErrorResult;
    fn append(self, nodes: Vec<NodeOrString>) -> ErrorResult;

    fn query_selector(self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>>;
    #[allow(unsafe_code)]
    unsafe fn query_selector_iter(self, selectors: DOMString) -> Fallible<QuerySelectorIterator>;
    fn query_selector_all(self, selectors: DOMString) -> Fallible<Temporary<NodeList>>;

    fn remove_self(self);

    fn get_unique_id(self) -> String;
    fn summarize(self) -> NodeInfo;

    fn teardown(self);

    fn parse_fragment(self, markup: DOMString) -> Fallible<Temporary<DocumentFragment>>;

}

impl<'a> NodeHelpers for JSRef<'a, Node> {
    fn teardown(self) {
        self.layout_data.dispose();
        for kid in self.children() {
            let kid = kid.root();
            kid.r().teardown();
        }
    }

    /// 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: u32) {
        let mut s = String::new();
        for _ in 0..indent {
            s.push_str("    ");
        }

        s.push_str(&*self.debug_str());
        debug!("{:?}", s);

        // FIXME: this should have a pure version?
        for kid in self.children() {
            let kid = kid.root();
            kid.r().dump_indent(indent + 1)
        }
    }

    /// Returns a string that describes this node.
    fn debug_str(self) -> String {
        format!("{:?}", self.type_id)
    }

    fn is_in_doc(self) -> bool {
        self.flags.get().contains(IS_IN_DOC)
    }

    /// Returns the type ID of this node. Fails if this node is borrowed mutably.
    fn type_id(self) -> NodeTypeId {
        self.type_id
    }

    // https://dom.spec.whatwg.org/#concept-node-length
    fn len(self) -> u32 {
        match self.type_id {
            NodeTypeId::DocumentType => 0,
            NodeTypeId::CharacterData(_) => {
                CharacterDataCast::to_ref(self).unwrap().Length()
            },
            _ => self.children().count() as u32
        }
    }

    // https://dom.spec.whatwg.org/#concept-tree-index
    fn index(self) -> u32 {
        self.preceding_siblings().count() as u32
    }

    #[inline]
    fn is_element(self) -> bool {
        match self.type_id {
            NodeTypeId::Element(..) => true,
            _ => false
        }
    }

    #[inline]
    fn is_document(self) -> bool {
        self.type_id == NodeTypeId::Document
    }

    #[inline]
    fn is_anchor_element(self) -> bool {
        self.type_id == NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement))
    }

    #[inline]
    fn is_doctype(self) -> bool {
        self.type_id == NodeTypeId::DocumentType
    }

    #[inline]
    fn is_text(self) -> bool {
        self.type_id == NodeTypeId::CharacterData(CharacterDataTypeId::Text)
    }

    fn get_flag(self, flag: NodeFlags) -> bool {
        self.flags.get().contains(flag)
    }

    fn set_flag(self, flag: NodeFlags, value: bool) {
        let mut flags = self.flags.get();

        if value {
            flags.insert(flag);
        } else {
            flags.remove(flag);
        }

        self.flags.set(flags);
    }

    fn get_hover_state(self) -> bool {
        self.get_flag(IN_HOVER_STATE)
    }

    fn set_hover_state(self, state: bool) {
        self.set_flag(IN_HOVER_STATE, state);
        self.dirty(NodeDamage::OtherNodeDamage);
    }

    fn get_focus_state(self) -> bool {
        self.get_flag(IN_FOCUS_STATE)
    }

    fn set_focus_state(self, state: bool) {
        self.set_flag(IN_FOCUS_STATE, state);
        self.dirty(NodeDamage::OtherNodeDamage);
    }

    fn get_disabled_state(self) -> bool {
        self.get_flag(IN_DISABLED_STATE)
    }

    fn set_disabled_state(self, state: bool) {
        self.set_flag(IN_DISABLED_STATE, state)
    }

    fn get_enabled_state(self) -> bool {
        self.get_flag(IN_ENABLED_STATE)
    }

    fn set_enabled_state(self, state: bool) {
        self.set_flag(IN_ENABLED_STATE, state)
    }

    fn get_has_changed(self) -> bool {
        self.get_flag(HAS_CHANGED)
    }

    fn set_has_changed(self, state: bool) {
        self.set_flag(HAS_CHANGED, state)
    }

    fn get_is_dirty(self) -> bool {
        self.get_flag(IS_DIRTY)
    }

    fn set_is_dirty(self, state: bool) {
        self.set_flag(IS_DIRTY, state)
    }

    fn get_has_dirty_siblings(self) -> bool {
        self.get_flag(HAS_DIRTY_SIBLINGS)
    }

    fn set_has_dirty_siblings(self, state: bool) {
        self.set_flag(HAS_DIRTY_SIBLINGS, state)
    }

    fn get_has_dirty_descendants(self) -> bool {
        self.get_flag(HAS_DIRTY_DESCENDANTS)
    }

    fn set_has_dirty_descendants(self, state: bool) {
        self.set_flag(HAS_DIRTY_DESCENDANTS, state)
    }

    fn force_dirty_ancestors(self, damage: NodeDamage) {
        self.dirty_impl(damage, true)
    }

    fn dirty(self, damage: NodeDamage) {
        self.dirty_impl(damage, false)
    }

    fn dirty_impl(self, damage: NodeDamage, force_ancestors: bool) {
        // 1. Dirty self.
        match damage {
            NodeDamage::NodeStyleDamaged => {}
            NodeDamage::OtherNodeDamage => self.set_has_changed(true),
        }

        if self.get_is_dirty() && !force_ancestors {
            return
        }

        // 2. Dirty descendants.
        fn dirty_subtree(node: JSRef<Node>) {
            // Stop if this subtree is already dirty.
            if node.get_is_dirty() { return }

            node.set_flag(IS_DIRTY | HAS_DIRTY_SIBLINGS | HAS_DIRTY_DESCENDANTS, true);

            for kid in node.children() {
                let kid = kid.root();
                dirty_subtree(kid.r());
            }
        }

        dirty_subtree(self);

        // 3. Dirty siblings.
        //
        // TODO(cgaebel): This is a very conservative way to account for sibling
        // selectors. Maybe we can do something smarter in the future.
        if !self.get_has_dirty_siblings() {
            let parent =
                match self.parent_node.get() {
                    None         => return,
                    Some(parent) => parent,
                }.root();

            for sibling in parent.r().children() {
                let sibling = sibling.root();
                sibling.r().set_has_dirty_siblings(true);
            }
        }

        // 4. Dirty ancestors.
        for ancestor in self.ancestors() {
            let ancestor = ancestor.root();
            if !force_ancestors && ancestor.r().get_has_dirty_descendants() { break }
            ancestor.r().set_has_dirty_descendants(true);
        }
    }

    /// Iterates over this node and all its descendants, in preorder.
    fn traverse_preorder(self) -> TreeIterator {
        TreeIterator::new(self)
    }

    fn inclusively_following_siblings(self) -> NodeSiblingIterator {
        NodeSiblingIterator {
            current: Some(Temporary::from_rooted(self)),
        }
    }

    fn inclusively_preceding_siblings(self) -> ReverseSiblingIterator {
        ReverseSiblingIterator {
            current: Some(Temporary::from_rooted(self)),
        }
    }

    fn is_inclusive_ancestor_of(self, parent: JSRef<Node>) -> bool {
        self == parent || parent.ancestors().any(|ancestor| ancestor.root().r() == self)
    }

    fn following_siblings(self) -> NodeSiblingIterator {
        NodeSiblingIterator {
            current: self.GetNextSibling(),
        }
    }

    fn preceding_siblings(self) -> ReverseSiblingIterator {
        ReverseSiblingIterator {
            current: self.GetPreviousSibling(),
        }
    }

    fn is_parent_of(self, child: JSRef<Node>) -> bool {
        match child.parent_node.get().root() {
            Some(ref parent) => parent.r() == self,
            None => false,
        }
    }

    fn to_trusted_node_address(self) -> TrustedNodeAddress {
        TrustedNodeAddress(&*self as *const Node as *const libc::c_void)
    }

    fn get_bounding_content_box(self) -> Rect<Au> {
        window_from_node(self).root().r().content_box_query(self.to_trusted_node_address())
    }

    fn get_content_boxes(self) -> Vec<Rect<Au>> {
        window_from_node(self).root().r().content_boxes_query(self.to_trusted_node_address())
    }

    // https://dom.spec.whatwg.org/#dom-childnode-before
    fn before(self, nodes: Vec<NodeOrString>) -> ErrorResult {
        match self.parent_node.get().root() {
            None => {
                // Step 1.
                Ok(())
            },
            Some(ref parent_node) => {
                // Step 2.
                let doc = self.owner_doc().root();
                let node = try!(doc.r().node_from_nodes_and_strings(nodes)).root();
                // Step 3.
                Node::pre_insert(node.r(), parent_node.r(),
                                 Some(self)).map(|_| ())
            },
        }
    }

    // https://dom.spec.whatwg.org/#dom-childnode-after
    fn after(self, nodes: Vec<NodeOrString>) -> ErrorResult {
        match self.parent_node.get().root() {
            None => {
                // Step 1.
                Ok(())
            },
            Some(ref parent_node) => {
                // Step 2.
                let doc = self.owner_doc().root();
                let node = try!(doc.r().node_from_nodes_and_strings(nodes)).root();
                // Step 3.
                // FIXME(https://github.com/servo/servo/issues/5720)
                let next_sibling = self.next_sibling.get().root();
                Node::pre_insert(node.r(), parent_node.r(),
                                 next_sibling.r()).map(|_| ())
            },
        }
    }

    // https://dom.spec.whatwg.org/#dom-childnode-replacewith
    fn replace_with(self, nodes: Vec<NodeOrString>) -> ErrorResult {
        match self.parent_node.get().root() {
            None => {
                // Step 1.
                Ok(())
            },
            Some(ref parent_node) => {
                // Step 2.
                let doc = self.owner_doc().root();
                let node = try!(doc.r().node_from_nodes_and_strings(nodes)).root();
                // Step 3.
                parent_node.r().ReplaceChild(node.r(), self).map(|_| ())
            },
        }
    }

    // https://dom.spec.whatwg.org/#dom-parentnode-prepend
    fn prepend(self, nodes: Vec<NodeOrString>) -> ErrorResult {
        // Step 1.
        let doc = self.owner_doc().root();
        let node = try!(doc.r().node_from_nodes_and_strings(nodes)).root();
        // Step 2.
        let first_child = self.first_child.get().root();
        Node::pre_insert(node.r(), self, first_child.r()).map(|_| ())
    }

    // https://dom.spec.whatwg.org/#dom-parentnode-append
    fn append(self, nodes: Vec<NodeOrString>) -> ErrorResult {
        // Step 1.
        let doc = self.owner_doc().root();
        let node = try!(doc.r().node_from_nodes_and_strings(nodes)).root();
        // Step 2.
        self.AppendChild(node.r()).map(|_| ())
    }

    // https://dom.spec.whatwg.org/#dom-parentnode-queryselector
    fn query_selector(self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> {
        // Step 1.
        match parse_author_origin_selector_list_from_str(&selectors) {
            // Step 2.
            Err(()) => return Err(Syntax),
            // Step 3.
            Ok(ref selectors) => {
                let root = self.ancestors().last().root();
                let root = root.r().unwrap_or(self.clone());
                Ok(root.traverse_preorder().filter_map(ElementCast::to_temporary).find(|element| {
                    matches(selectors, &NodeCast::from_ref(element.root().r()), &mut None)
                }))
            }
        }
    }

    /// Get an iterator over all nodes which match a set of selectors
    /// Be careful not to do anything which may manipulate the DOM tree whilst iterating, otherwise
    /// the iterator may be invalidated
    #[allow(unsafe_code)]
    unsafe fn query_selector_iter(self, selectors: DOMString)
                                  -> Fallible<QuerySelectorIterator> {
        // Step 1.
        match parse_author_origin_selector_list_from_str(&selectors) {
            // Step 2.
            Err(()) => Err(Syntax),
            // Step 3.
            Ok(selectors) => {
                let root = self.ancestors().last().root();
                let root = root.r().unwrap_or(self);
                Ok(QuerySelectorIterator::new(root.traverse_preorder(), selectors))
            }
        }
    }

    // https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
    #[allow(unsafe_code)]
    fn query_selector_all(self, selectors: DOMString) -> Fallible<Temporary<NodeList>> {
        let mut nodes = RootedVec::new();
        for node in try!(unsafe { self.query_selector_iter(selectors) }) {
            nodes.push(JS::from_rooted(node));
        }
        let window = window_from_node(self).root();
        Ok(NodeList::new_simple_list(window.r(), &nodes))
    }


    fn ancestors(self) -> AncestorIterator {
        AncestorIterator {
            current: self.GetParentNode()
        }
    }

    fn inclusive_ancestors(self) -> AncestorIterator {
        AncestorIterator {
            current: Some(Temporary::from_rooted(self))
        }
    }

    fn owner_doc(self) -> Temporary<Document> {
        Temporary::from_rooted(self.owner_doc.get().unwrap())
    }

    fn set_owner_doc(self, document: JSRef<Document>) {
        self.owner_doc.set(Some(JS::from_rooted(document)));
    }

    fn is_in_html_doc(self) -> bool {
        self.owner_doc().root().r().is_html_document()
    }

    fn children(self) -> NodeSiblingIterator {
        NodeSiblingIterator {
            current: self.GetFirstChild(),
        }
    }

    fn rev_children(self) -> ReverseSiblingIterator {
        ReverseSiblingIterator {
            current: self.GetLastChild(),
        }
    }

    fn child_elements(self) -> ChildElementIterator {
        fn to_temporary(node: Temporary<Node>) -> Option<Temporary<Element>> {
            ElementCast::to_temporary(node)
        }
        self.children()
            .filter_map(to_temporary as fn(_) -> _)
            .peekable()
    }

    fn remove_self(self) {
        match self.parent_node.get().root() {
            Some(ref parent) => parent.r().remove_child(self),
            None => ()
        }
    }

    fn get_unique_id(self) -> String {
        // FIXME(https://github.com/rust-lang/rust/issues/23338)
        let id = self.unique_id.borrow();
        id.clone()
    }

    fn summarize(self) -> NodeInfo {
        if self.unique_id.borrow().is_empty() {
            let mut unique_id = self.unique_id.borrow_mut();
            *unique_id = uuid::Uuid::new_v4().to_simple_string();
        }

        // FIXME(https://github.com/rust-lang/rust/issues/23338)
        let unique_id = self.unique_id.borrow();
        NodeInfo {
            uniqueId: unique_id.clone(),
            baseURI: self.GetBaseURI().unwrap_or("".to_owned()),
            parent: self.GetParentNode().root().map(|node| node.r().get_unique_id()).unwrap_or("".to_owned()),
            nodeType: self.NodeType(),
            namespaceURI: "".to_owned(), //FIXME
            nodeName: self.NodeName(),
            numChildren: self.ChildNodes().root().r().Length() as usize,

            //FIXME doctype nodes only
            name: "".to_owned(),
            publicId: "".to_owned(),
            systemId: "".to_owned(),

            attrs: {
                let e: Option<JSRef<Element>> = ElementCast::to_ref(self);
                match e {
                    Some(element) => element.summarize(),
                    None => vec!(),
                }
            },

            isDocumentElement:
                self.owner_doc().root()
                    .r()
                    .GetDocumentElement()
                    .map(|elem| NodeCast::from_ref(elem.root().r()) == self)
                    .unwrap_or(false),

            shortValue: self.GetNodeValue().unwrap_or("".to_owned()), //FIXME: truncate
            incompleteValue: false, //FIXME: reflect truncation
        }
    }

    // https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#dfn-concept-parse-fragment
    fn parse_fragment(self, markup: DOMString) -> Fallible<Temporary<DocumentFragment>> {
        let context_node: JSRef<Node> = NodeCast::from_ref(self);
        let context_document = document_from_node(self).root();
        let mut new_children: RootedVec<JS<Node>> = RootedVec::new();
        if context_document.r().is_html_document() {
            parse_html_fragment(context_node, markup, &mut new_children);
        } else {
            // FIXME: XML case
            unimplemented!();
        }
        let fragment = DocumentFragment::new(context_document.r()).root();
        let fragment_node: JSRef<Node> = NodeCast::from_ref(fragment.r());
        for node in new_children.iter() {
            fragment_node.AppendChild(node.root().r()).unwrap();
        }
        Ok(Temporary::from_rooted(fragment.r()))
    }
}

/// If the given untrusted node address represents a valid DOM node in the given runtime,
/// returns it.
#[allow(unsafe_code)]
pub fn from_untrusted_node_address(runtime: *mut JSRuntime, candidate: UntrustedNodeAddress)
    -> Temporary<Node> {
    unsafe {
        let candidate: uintptr_t = mem::transmute(candidate.0);
        let object: *mut JSObject = jsfriendapi::bindgen::JS_GetAddressableObject(runtime,
                                                                                  candidate);
        if object.is_null() {
            panic!("Attempted to create a `JS<Node>` from an invalid pointer!")
        }
        let boxed_node: *const Node = conversions::native_from_reflector(object);
        Temporary::from_unrooted(Unrooted::from_raw(boxed_node))
    }
}

pub trait LayoutNodeHelpers {
    #[allow(unsafe_code)]
    unsafe fn type_id_for_layout(&self) -> NodeTypeId;

    #[allow(unsafe_code)]
    unsafe fn parent_node_ref(&self) -> Option<LayoutJS<Node>>;
    #[allow(unsafe_code)]
    unsafe fn first_child_ref(&self) -> Option<LayoutJS<Node>>;
    #[allow(unsafe_code)]
    unsafe fn last_child_ref(&self) -> Option<LayoutJS<Node>>;
    #[allow(unsafe_code)]
    unsafe fn prev_sibling_ref(&self) -> Option<LayoutJS<Node>>;
    #[allow(unsafe_code)]
    unsafe fn next_sibling_ref(&self) -> Option<LayoutJS<Node>>;

    #[allow(unsafe_code)]
    unsafe fn owner_doc_for_layout(&self) -> LayoutJS<Document>;

    #[allow(unsafe_code)]
    unsafe fn is_element_for_layout(&self) -> bool;
    #[allow(unsafe_code)]
    unsafe fn get_flag(self, flag: NodeFlags) -> bool;
    #[allow(unsafe_code)]
    unsafe fn set_flag(self, flag: NodeFlags, value: bool);
}

impl LayoutNodeHelpers for LayoutJS<Node> {
    #[inline]
    #[allow(unsafe_code)]
    unsafe fn type_id_for_layout(&self) -> NodeTypeId {
        (*self.unsafe_get()).type_id
    }

    #[inline]
    #[allow(unsafe_code)]
    unsafe fn is_element_for_layout(&self) -> bool {
        (*self.unsafe_get()).is_element()
    }

    #[inline]
    #[allow(unsafe_code)]
    unsafe fn parent_node_ref(&self) -> Option<LayoutJS<Node>> {
        (*self.unsafe_get()).parent_node.get_inner_as_layout()
    }

    #[inline]
    #[allow(unsafe_code)]
    unsafe fn first_child_ref(&self) -> Option<LayoutJS<Node>> {
        (*self.unsafe_get()).first_child.get_inner_as_layout()
    }

    #[inline]
    #[allow(unsafe_code)]
    unsafe fn last_child_ref(&self) -> Option<LayoutJS<Node>> {
        (*self.unsafe_get()).last_child.get_inner_as_layout()
    }

    #[inline]
    #[allow(unsafe_code)]
    unsafe fn prev_sibling_ref(&self) -> Option<LayoutJS<Node>> {
        (*self.unsafe_get()).prev_sibling.get_inner_as_layout()
    }

    #[inline]
    #[allow(unsafe_code)]
    unsafe fn next_sibling_ref(&self) -> Option<LayoutJS<Node>> {
        (*self.unsafe_get()).next_sibling.get_inner_as_layout()
    }

    #[inline]
    #[allow(unsafe_code)]
    unsafe fn owner_doc_for_layout(&self) -> LayoutJS<Document> {
        (*self.unsafe_get()).owner_doc.get_inner_as_layout().unwrap()
    }

    #[inline]
    #[allow(unsafe_code)]
    unsafe fn get_flag(self, flag: NodeFlags) -> bool {
        (*self.unsafe_get()).flags.get().contains(flag)
    }

    #[inline]
    #[allow(unsafe_code)]
    unsafe fn set_flag(self, flag: NodeFlags, value: bool) {
        let this = self.unsafe_get();
        let mut flags = (*this).flags.get();

        if value {
            flags.insert(flag);
        } else {
            flags.remove(flag);
        }

        (*this).flags.set(flags);
    }
}

pub trait RawLayoutNodeHelpers {
    #[allow(unsafe_code)]
    unsafe fn get_hover_state_for_layout(&self) -> bool;
    #[allow(unsafe_code)]
    unsafe fn get_focus_state_for_layout(&self) -> bool;
    #[allow(unsafe_code)]
    unsafe fn get_disabled_state_for_layout(&self) -> bool;
    #[allow(unsafe_code)]
    unsafe fn get_enabled_state_for_layout(&self) -> bool;
    fn type_id_for_layout(&self) -> NodeTypeId;
}

impl RawLayoutNodeHelpers for Node {
    #[inline]
    #[allow(unsafe_code)]
    unsafe fn get_hover_state_for_layout(&self) -> bool {
        self.flags.get().contains(IN_HOVER_STATE)
    }
    #[inline]
    #[allow(unsafe_code)]
    unsafe fn get_focus_state_for_layout(&self) -> bool {
        self.flags.get().contains(IN_FOCUS_STATE)
    }
    #[inline]
    #[allow(unsafe_code)]
    unsafe fn get_disabled_state_for_layout(&self) -> bool {
        self.flags.get().contains(IN_DISABLED_STATE)
    }
    #[inline]
    #[allow(unsafe_code)]
    unsafe fn get_enabled_state_for_layout(&self) -> bool {
        self.flags.get().contains(IN_ENABLED_STATE)
    }
    #[inline]
    fn type_id_for_layout(&self) -> NodeTypeId {
        self.type_id
    }
}


//
// Iteration and traversal
//

pub type ChildElementIterator =
    Peekable<FilterMap<NodeSiblingIterator,
                       fn(Temporary<Node>) -> Option<Temporary<Element>>>>;

pub struct NodeSiblingIterator {
    current: Option<Temporary<Node>>,
}

impl Iterator for NodeSiblingIterator {
    type Item = Temporary<Node>;

    fn next(&mut self) -> Option<Temporary<Node>> {
        let current = match self.current.take() {
            None => return None,
            Some(current) => current,
        }.root();
        self.current = current.r().GetNextSibling();
        Some(Temporary::from_rooted(current.r()))
    }
}

pub struct ReverseSiblingIterator {
    current: Option<Temporary<Node>>,
}

impl Iterator for ReverseSiblingIterator {
    type Item = Temporary<Node>;

    fn next(&mut self) -> Option<Temporary<Node>> {
        let current = match self.current.take() {
            None => return None,
            Some(current) => current,
        }.root();
        self.current = current.r().GetPreviousSibling();
        Some(Temporary::from_rooted(current.r()))
    }
}

pub struct AncestorIterator {
    current: Option<Temporary<Node>>,
}

impl Iterator for AncestorIterator {
    type Item = Temporary<Node>;

    fn next(&mut self) -> Option<Temporary<Node>> {
        let current = match self.current.take() {
            None => return None,
            Some(current) => current,
        }.root();
        self.current = current.r().GetParentNode();
        Some(Temporary::from_rooted(current.r()))
    }
}

pub struct TreeIterator {
    current: Option<Temporary<Node>>,
    depth: usize,
}

impl<'a> TreeIterator {
    fn new(root: JSRef<'a, Node>) -> TreeIterator {
        TreeIterator {
            current: Some(Temporary::from_rooted(root)),
            depth: 0,
        }
    }
}

impl Iterator for TreeIterator {
    type Item = Temporary<Node>;

    // https://dom.spec.whatwg.org/#concept-tree-order
    fn next(&mut self) -> Option<Temporary<Node>> {
        let current = match self.current.take() {
            None => return None,
            Some(current) => current,
        };
        let node = current.root();
        if let Some(first_child) = node.r().GetFirstChild() {
            self.current = Some(first_child);
            self.depth += 1;
            return Some(current);
        };
        for ancestor in node.r().inclusive_ancestors() {
            if self.depth == 0 {
                break;
            }
            if let Some(next_sibling) = ancestor.root().r().GetNextSibling() {
                self.current = Some(next_sibling);
                return Some(current);
            }
            self.depth -= 1;
        }
        debug_assert!(self.depth == 0);
        self.current = None;
        Some(current)
    }
}


/// Specifies whether children must be recursively cloned or not.
#[derive(Copy, Clone, 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, GlobalRef::Window(window.r()), 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(EventTargetTypeId::Node(type_id)),
            type_id: type_id,

            parent_node: Default::default(),
            first_child: Default::default(),
            last_child: Default::default(),
            next_sibling: Default::default(),
            prev_sibling: Default::default(),
            owner_doc: MutNullableHeap::new(doc.map(JS::from_rooted)),
            child_list: Default::default(),
            flags: Cell::new(NodeFlags::new(type_id)),

            layout_data: LayoutDataRef::new(),

            unique_id: DOMRefCell::new(String::new()),
        }
    }

    #[inline]
    pub fn layout_data(&self) -> Ref<Option<LayoutData>> {
        self.layout_data.borrow()
    }

    #[inline]
    pub fn layout_data_mut(&self) -> RefMut<Option<LayoutData>> {
        self.layout_data.borrow_mut()
    }

    #[inline]
    #[allow(unsafe_code)]
    pub unsafe fn layout_data_unchecked(&self) -> *const Option<LayoutData> {
        self.layout_data.borrow_unchecked()
    }

    // https://dom.spec.whatwg.org/#concept-node-adopt
    pub fn adopt(node: JSRef<Node>, document: JSRef<Document>) {
        // Step 1.
        match node.parent_node.get().root() {
            Some(ref parent) => {
                Node::remove(node, parent.r(), SuppressObserver::Unsuppressed);
            }
            None => (),
        }

        // Step 2.
        let node_doc = document_from_node(node).root();
        if node_doc.r() != document {
            for descendant in node.traverse_preorder() {
                descendant.root().r().set_owner_doc(document);
            }
        }

        // Step 3.
        // If node is an element, it is _affected by a base URL change_.
    }

    // https://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() {
            NodeTypeId::Document |
            NodeTypeId::DocumentFragment |
            NodeTypeId::Element(..) => (),
            _ => return Err(HierarchyRequest)
        }

        // Step 2.
        if node.is_inclusive_ancestor_of(parent) {
            return Err(HierarchyRequest);
        }

        // Step 3.
        if let Some(child) = child {
            if !parent.is_parent_of(child) {
                return Err(NotFound);
            }
        }

        // Step 4-5.
        match node.type_id() {
            NodeTypeId::CharacterData(CharacterDataTypeId::Text) => {
                if parent.is_document() {
                    return Err(HierarchyRequest);
                }
            },
            NodeTypeId::DocumentType => {
                if !parent.is_document() {
                    return Err(HierarchyRequest);
                }
            },
            NodeTypeId::DocumentFragment |
            NodeTypeId::Element(_) |
            NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) |
            NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => (),
            NodeTypeId::Document => return Err(HierarchyRequest)
        }

        // Step 6.
        match parent.type_id() {
            NodeTypeId::Document => {
                match node.type_id() {
                    // Step 6.1
                    NodeTypeId::DocumentFragment => {
                        // Step 6.1.1(b)
                        if node.children()
                               .map(|c| c.root())
                               .any(|c| c.r().is_text())
                        {
                            return Err(HierarchyRequest);
                        }
                        match node.child_elements().count() {
                            0 => (),
                            // Step 6.1.2
                            1 => {
                                if !parent.child_elements().is_empty() {
                                    return Err(HierarchyRequest);
                                }
                                if let Some(child) = child {
                                    if child.inclusively_following_siblings()
                                        .map(|c| c.root())
                                        .any(|child| child.r().is_doctype()) {
                                            return Err(HierarchyRequest);
                                    }
                                }
                            },
                            // Step 6.1.1(a)
                            _ => return Err(HierarchyRequest),
                        }
                    },
                    // Step 6.2
                    NodeTypeId::Element(_) => {
                        if !parent.child_elements().is_empty() {
                            return Err(HierarchyRequest);
                        }
                        if let Some(ref child) = child {
                            if child.inclusively_following_siblings()
                                .map(|c| c.root())
                                .any(|child| child.r().is_doctype()) {
                                    return Err(HierarchyRequest);
                            }
                        }
                    },
                    // Step 6.3
                    NodeTypeId::DocumentType => {
                        if parent.children()
                                 .map(|c| c.root())
                                 .any(|c| c.r().is_doctype())
                        {
                            return Err(HierarchyRequest);
                        }
                        match child {
                            Some(child) => {
                                if parent.children()
                                         .map(|c| c.root())
                                         .take_while(|c| c.r() != child)
                                         .any(|c| c.r().is_element())
                                {
                                    return Err(HierarchyRequest);
                                }
                            },
                            None => {
                                if !parent.child_elements().is_empty() {
                                    return Err(HierarchyRequest);
                                }
                            },
                        }
                    },
                    NodeTypeId::CharacterData(_) => (),
                    NodeTypeId::Document => unreachable!(),
                }
            },
            _ => (),
        }

        // Step 7-8.
        let reference_child = match child {
            Some(child) if child == node => node.GetNextSibling(),
            _ => None
        }.root();
        let reference_child = reference_child.r().or(child);

        // Step 9.
        let document = document_from_node(parent).root();
        Node::adopt(node, document.r());

        // Step 10.
        Node::insert(node, parent, reference_child, SuppressObserver::Unsuppressed);

        // Step 11.
        return Ok(Temporary::from_rooted(node))
    }

    // https://dom.spec.whatwg.org/#concept-node-insert
    fn insert(node: JSRef<Node>,
              parent: JSRef<Node>,
              child: Option<JSRef<Node>>,
              suppress_observers: SuppressObserver) {
        fn do_insert(node: JSRef<Node>, parent: JSRef<Node>, child: Option<JSRef<Node>>) {
            parent.add_child(node, child);
            let is_in_doc = parent.is_in_doc();
            for kid in node.traverse_preorder() {
                let kid = kid.root();
                let mut flags = kid.r().flags.get();
                if is_in_doc {
                    flags.insert(IS_IN_DOC);
                } else {
                    flags.remove(IS_IN_DOC);
                }
                kid.r().flags.set(flags);
            }
        }

        fn fire_observer_if_necessary(node: JSRef<Node>, suppress_observers: SuppressObserver) {
            match suppress_observers {
                SuppressObserver::Unsuppressed => node.node_inserted(),
                SuppressObserver::Suppressed => ()
            }
        }

        // XXX assert owner_doc
        // Step 1-3: ranges.

        match node.type_id() {
            NodeTypeId::DocumentFragment => {
                // Step 4.
                // Step 5: DocumentFragment, mutation records.
                // Step 6: DocumentFragment.
                let mut kids = Vec::new();
                for kid in node.children() {
                    let kid = kid.root();
                    kids.push(Temporary::from_rooted(kid.r()));
                    Node::remove(kid.r(), node, SuppressObserver::Suppressed);
                }

                // Step 7: mutation records.
                // Step 8.
                for kid in kids.clone().into_iter() {
                    let kid = kid.root();
                    do_insert(kid.r(), parent, child);
                }

                for kid in kids.into_iter() {
                    let kid = kid.root();
                    fire_observer_if_necessary(kid.r(), suppress_observers);
                }
            }
            _ => {
                // Step 4.
                // Step 5: DocumentFragment, mutation records.
                // Step 6: DocumentFragment.
                // Step 7: mutation records.
                // Step 8.
                do_insert(node, parent, child);
                // Step 9.
                fire_observer_if_necessary(node, suppress_observers);
            }
        }
    }

    // https://dom.spec.whatwg.org/#concept-node-replace-all
    pub fn replace_all(node: Option<JSRef<Node>>, parent: JSRef<Node>) {
        // Step 1.
        match node {
            Some(node) => {
                let document = document_from_node(parent).root();
                Node::adopt(node, document.r());
            }
            None => (),
        }

        // Step 2.
        let mut removed_nodes: RootedVec<JS<Node>> = RootedVec::new();
        for child in parent.children() {
            removed_nodes.push(JS::from_rooted(child));
        }

        // Step 3.
        let added_nodes = match node {
            None => vec!(),
            Some(node) => match node.type_id() {
                NodeTypeId::DocumentFragment => node.children().collect(),
                _ => vec!(Temporary::from_rooted(node)),
            },
        };

        // Step 4.
        for child in parent.children() {
            let child = child.root();
            Node::remove(child.r(), parent, SuppressObserver::Suppressed);
        }

        // Step 5.
        match node {
            Some(node) => Node::insert(node, parent, None, SuppressObserver::Suppressed),
            None => (),
        }

        // Step 6: mutation records.

        // Step 7.
        let parent_in_doc = parent.is_in_doc();
        for removed_node in removed_nodes.iter() {
            let removed_node = removed_node.root();
            removed_node.r().node_removed(parent_in_doc);
        }
        for added_node in added_nodes {
            let added_node = added_node.root();
            added_node.r().node_inserted();
        }
    }

    // https://dom.spec.whatwg.org/#concept-node-pre-remove
    fn pre_remove(child: JSRef<Node>, parent: JSRef<Node>) -> Fallible<Temporary<Node>> {
        // Step 1.
        match child.GetParentNode() {
            Some(ref node) if node != &Temporary::from_rooted(parent) => return Err(NotFound),
            None => return Err(NotFound),
            _ => ()
        }

        // Step 2.
        Node::remove(child, parent, SuppressObserver::Unsuppressed);

        // Step 3.
        Ok(Temporary::from_rooted(child))
    }

    // https://dom.spec.whatwg.org/#concept-node-remove
    fn remove(node: JSRef<Node>, parent: JSRef<Node>, suppress_observers: SuppressObserver) {
        assert!(node.GetParentNode().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.set_flag(IS_IN_DOC, false);

        // Step 9.
        match suppress_observers {
            SuppressObserver::Suppressed => (),
            SuppressObserver::Unsuppressed => node.node_removed(parent.is_in_doc()),
        }
    }

    // https://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() {
            NodeTypeId::DocumentType => {
                let doctype: JSRef<DocumentType> = DocumentTypeCast::to_ref(node).unwrap();
                let doctype = DocumentType::new(doctype.name().clone(),
                                                Some(doctype.public_id().clone()),
                                                Some(doctype.system_id().clone()), document.r());
                NodeCast::from_temporary(doctype)
            },
            NodeTypeId::DocumentFragment => {
                let doc_fragment = DocumentFragment::new(document.r());
                NodeCast::from_temporary(doc_fragment)
            },
            NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => {
                let cdata = CharacterDataCast::to_ref(node).unwrap();
                let comment = Comment::new(cdata.Data(), document.r());
                NodeCast::from_temporary(comment)
            },
            NodeTypeId::Document => {
                let document: JSRef<Document> = DocumentCast::to_ref(node).unwrap();
                let is_html_doc = match document.is_html_document() {
                    true => IsHTMLDocument::HTMLDocument,
                    false => IsHTMLDocument::NonHTMLDocument,
                };
                let window = document.window().root();
                let document = Document::new(window.r(), Some(document.url()),
                                             is_html_doc, None,
                                             None, DocumentSource::NotFromParser);
                NodeCast::from_temporary(document)
            },
            NodeTypeId::Element(..) => {
                let element: JSRef<Element> = ElementCast::to_ref(node).unwrap();
                let name = QualName {
                    ns: element.namespace().clone(),
                    local: element.local_name().clone()
                };
                let element = Element::create(name,
                    element.prefix().as_ref().map(|p| Atom::from_slice(&p)),
                    document.r(), ElementCreator::ScriptCreated);
                NodeCast::from_temporary(element)
            },
            NodeTypeId::CharacterData(CharacterDataTypeId::Text) => {
                let cdata = CharacterDataCast::to_ref(node).unwrap();
                let text = Text::new(cdata.Data(), document.r());
                NodeCast::from_temporary(text)
            },
            NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
                let pi: JSRef<ProcessingInstruction> = ProcessingInstructionCast::to_ref(node).unwrap();
                let pi = ProcessingInstruction::new(pi.Target(),
                                                    CharacterDataCast::from_ref(pi).Data(), document.r());
                NodeCast::from_temporary(pi)
            },
        }.root();

        // Step 3.
        let document = match DocumentCast::to_ref(copy.r()) {
            Some(doc) => doc,
            None => document.r(),
        };
        assert!(copy.r().owner_doc().root().r() == document);

        // Step 4 (some data already copied in step 2).
        match node.type_id() {
            NodeTypeId::Document => {
                let node_doc: JSRef<Document> = DocumentCast::to_ref(node).unwrap();
                let copy_doc: JSRef<Document> = DocumentCast::to_ref(copy.r()).unwrap();
                copy_doc.set_encoding_name(node_doc.encoding_name().clone());
                copy_doc.set_quirks_mode(node_doc.quirks_mode());
            },
            NodeTypeId::Element(..) => {
                let node_elem: JSRef<Element> = ElementCast::to_ref(node).unwrap();
                let copy_elem: JSRef<Element> = ElementCast::to_ref(copy.r()).unwrap();

                // FIXME: https://github.com/mozilla/servo/issues/1737
                let window = document.window().root();
                for ref attr in node_elem.attrs().iter().map(|attr| attr.root()) {
                    copy_elem.attrs_mut().push_unrooted(
                        &Attr::new(window.r(),
                                   attr.r().local_name().clone(), attr.r().value().clone(),
                                   attr.r().name().clone(), attr.r().namespace().clone(),
                                   attr.r().prefix().clone(), Some(copy_elem)));
                }
            },
            _ => ()
        }

        // Step 5: cloning steps.
        vtable_for(&node).cloning_steps(copy.r(), maybe_doc, clone_children);

        // Step 6.
        if clone_children == CloneChildrenFlag::CloneChildren {
            for child in node.children() {
                let child = child.root();
                let child_copy = Node::clone(child.r(), Some(document),
                                             clone_children).root();
                let _inserted_node = Node::pre_insert(child_copy.r(), copy.r(), None);
            }
        }

        // Step 7.
        Temporary::from_rooted(copy.r())
    }

    pub fn collect_text_contents<T: Iterator<Item=Temporary<Node>>>(iterator: T) -> String {
        let mut content = String::new();
        for node in iterator {
            let node = node.root();
            let text = TextCast::to_ref(node.r());
            match text {
                Some(text) => content.push_str(&CharacterDataCast::from_ref(text).Data()),
                None => (),
            }
        }
        content
    }
}

impl<'a> NodeMethods for JSRef<'a, Node> {
    // https://dom.spec.whatwg.org/#dom-node-nodetype
    fn NodeType(self) -> u16 {
        match self.type_id {
            NodeTypeId::CharacterData(CharacterDataTypeId::Text)                  => NodeConstants::TEXT_NODE,
            NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => NodeConstants::PROCESSING_INSTRUCTION_NODE,
            NodeTypeId::CharacterData(CharacterDataTypeId::Comment)               => NodeConstants::COMMENT_NODE,
            NodeTypeId::Document                                                  => NodeConstants::DOCUMENT_NODE,
            NodeTypeId::DocumentType                                              => NodeConstants::DOCUMENT_TYPE_NODE,
            NodeTypeId::DocumentFragment                                          => NodeConstants::DOCUMENT_FRAGMENT_NODE,
            NodeTypeId::Element(_)                                                => NodeConstants::ELEMENT_NODE,
        }
    }

    // https://dom.spec.whatwg.org/#dom-node-nodename
    fn NodeName(self) -> DOMString {
        match self.type_id {
            NodeTypeId::Element(..) => {
                let elem: JSRef<Element> = ElementCast::to_ref(self).unwrap();
                elem.TagName()
            }
            NodeTypeId::CharacterData(CharacterDataTypeId::Text) => "#text".to_owned(),
            NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
                let processing_instruction: JSRef<ProcessingInstruction> =
                    ProcessingInstructionCast::to_ref(self).unwrap();
                processing_instruction.Target()
            }
            NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => "#comment".to_owned(),
            NodeTypeId::DocumentType => {
                let doctype: JSRef<DocumentType> = DocumentTypeCast::to_ref(self).unwrap();
                doctype.name().clone()
            },
            NodeTypeId::DocumentFragment => "#document-fragment".to_owned(),
            NodeTypeId::Document => "#document".to_owned()
        }
    }

    // https://dom.spec.whatwg.org/#dom-node-baseuri
    fn GetBaseURI(self) -> Option<DOMString> {
        // FIXME (#1824) implement.
        None
    }

    // https://dom.spec.whatwg.org/#dom-node-ownerdocument
    fn GetOwnerDocument(self) -> Option<Temporary<Document>> {
        match self.type_id {
            NodeTypeId::CharacterData(..) |
            NodeTypeId::Element(..) |
            NodeTypeId::DocumentType |
            NodeTypeId::DocumentFragment => Some(self.owner_doc()),
            NodeTypeId::Document => None
        }
    }

    // https://dom.spec.whatwg.org/#dom-node-parentnode
    fn GetParentNode(self) -> Option<Temporary<Node>> {
        self.parent_node.get().map(Temporary::from_rooted)
    }

    // https://dom.spec.whatwg.org/#dom-node-parentelement
    fn GetParentElement(self) -> Option<Temporary<Element>> {
        self.GetParentNode().and_then(ElementCast::to_temporary)
    }

    // https://dom.spec.whatwg.org/#dom-node-haschildnodes
    fn HasChildNodes(self) -> bool {
        self.first_child.get().is_some()
    }

    // https://dom.spec.whatwg.org/#dom-node-childnodes
    fn ChildNodes(self) -> Temporary<NodeList> {
        self.child_list.or_init(|| {
            let doc = self.owner_doc().root();
            let window = doc.r().window().root();
            NodeList::new_child_list(window.r(), self)
        })
    }

    // https://dom.spec.whatwg.org/#dom-node-firstchild
    fn GetFirstChild(self) -> Option<Temporary<Node>> {
        self.first_child.get().map(Temporary::from_rooted)
    }

    // https://dom.spec.whatwg.org/#dom-node-lastchild
    fn GetLastChild(self) -> Option<Temporary<Node>> {
        self.last_child.get().map(Temporary::from_rooted)
    }

    // https://dom.spec.whatwg.org/#dom-node-previoussibling
    fn GetPreviousSibling(self) -> Option<Temporary<Node>> {
        self.prev_sibling.get().map(Temporary::from_rooted)
    }

    // https://dom.spec.whatwg.org/#dom-node-nextsibling
    fn GetNextSibling(self) -> Option<Temporary<Node>> {
        self.next_sibling.get().map(Temporary::from_rooted)
    }

    // https://dom.spec.whatwg.org/#dom-node-nodevalue
    fn GetNodeValue(self) -> Option<DOMString> {
        match self.type_id {
            NodeTypeId::CharacterData(..) => {
                let chardata: JSRef<CharacterData> = CharacterDataCast::to_ref(self).unwrap();
                Some(chardata.Data())
            }
            _ => {
                None
            }
        }
    }

    // https://dom.spec.whatwg.org/#dom-node-nodevalue
    fn SetNodeValue(self, val: Option<DOMString>) {
        match self.type_id {
            NodeTypeId::CharacterData(..) => {
                self.SetTextContent(val)
            }
            _ => {}
        }
    }

    // https://dom.spec.whatwg.org/#dom-node-textcontent
    fn GetTextContent(self) -> Option<DOMString> {
        match self.type_id {
            NodeTypeId::DocumentFragment |
            NodeTypeId::Element(..) => {
                let content = Node::collect_text_contents(self.traverse_preorder());
                Some(content)
            }
            NodeTypeId::CharacterData(..) => {
                let characterdata: JSRef<CharacterData> = CharacterDataCast::to_ref(self).unwrap();
                Some(characterdata.Data())
            }
            NodeTypeId::DocumentType |
            NodeTypeId::Document => {
                None
            }
        }
    }

    // https://dom.spec.whatwg.org/#dom-node-textcontent
    fn SetTextContent(self, value: Option<DOMString>) {
        let value = null_str_as_empty(&value);
        match self.type_id {
            NodeTypeId::DocumentFragment |
            NodeTypeId::Element(..) => {
                // Step 1-2.
                let node = if value.len() == 0 {
                    None
                } else {
                    let document = self.owner_doc().root();
                    Some(NodeCast::from_temporary(document.r().CreateTextNode(value)))
                }.root();

                // Step 3.
                Node::replace_all(node.r(), self);
            }
            NodeTypeId::CharacterData(..) => {
                let characterdata: JSRef<CharacterData> = CharacterDataCast::to_ref(self).unwrap();
                characterdata.SetData(value);

                // Notify the document that the content of this node is different
                let document = self.owner_doc().root();
                document.r().content_changed(self, NodeDamage::OtherNodeDamage);
            }
            NodeTypeId::DocumentType |
            NodeTypeId::Document => {}
        }
    }

    // https://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)
    }

    // https://dom.spec.whatwg.org/#dom-node-appendchild
    fn AppendChild(self, node: JSRef<Node>) -> Fallible<Temporary<Node>> {
        Node::pre_insert(node, self, None)
    }

    // https://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 {
            NodeTypeId::Document |
            NodeTypeId::DocumentFragment |
            NodeTypeId::Element(..) => (),
            _ => 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() {
            NodeTypeId::CharacterData(CharacterDataTypeId::Text) if self.is_document() => return Err(HierarchyRequest),
            NodeTypeId::DocumentType if !self.is_document() => return Err(HierarchyRequest),
            NodeTypeId::DocumentFragment |
            NodeTypeId::DocumentType |
            NodeTypeId::Element(..) |
            NodeTypeId::CharacterData(..) => (),
            NodeTypeId::Document => return Err(HierarchyRequest)
        }

        // Step 6.
        match self.type_id {
            NodeTypeId::Document => {
                match node.type_id() {
                    // Step 6.1
                    NodeTypeId::DocumentFragment => {
                        // Step 6.1.1(b)
                        if node.children()
                               .map(|c| c.root())
                               .any(|c| c.r().is_text())
                        {
                            return Err(HierarchyRequest);
                        }
                        match node.child_elements().count() {
                            0 => (),
                            // Step 6.1.2
                            1 => {
                                if self.child_elements()
                                       .map(|c| c.root())
                                       .any(|c| NodeCast::from_ref(c.r()) != child) {
                                    return Err(HierarchyRequest);
                                }
                                if child.following_siblings()
                                        .map(|c| c.root())
                                        .any(|child| child.r().is_doctype()) {
                                    return Err(HierarchyRequest);
                                }
                            },
                            // Step 6.1.1(a)
                            _ => return Err(HierarchyRequest)
                        }
                    },
                    // Step 6.2
                    NodeTypeId::Element(..) => {
                        if self.child_elements()
                               .map(|c| c.root())
                               .any(|c| NodeCast::from_ref(c.r()) != child) {
                            return Err(HierarchyRequest);
                        }
                        if child.following_siblings()
                                .map(|c| c.root())
                                .any(|child| child.r().is_doctype())
                        {
                            return Err(HierarchyRequest);
                        }
                    },
                    // Step 6.3
                    NodeTypeId::DocumentType => {
                        if self.children()
                               .map(|c| c.root())
                               .any(|c| c.r().is_doctype() && c.r() != child)
                        {
                            return Err(HierarchyRequest);
                        }
                        if self.children()
                               .map(|c| c.root())
                               .take_while(|c| c.r() != child)
                               .any(|c| c.r().is_element())
                        {
                            return Err(HierarchyRequest);
                        }
                    },
                    NodeTypeId::CharacterData(..) => (),
                    NodeTypeId::Document => unreachable!()
                }
            },
            _ => ()
        }

        // Ok if not caught by previous error checks.
        if node == child {
            return Ok(Temporary::from_rooted(child));
        }

        // Step 7-8.
        let child_next_sibling = child.next_sibling.get().root();
        let node_next_sibling = node.next_sibling.get().root();
        let reference_child = if child_next_sibling.r() == Some(node) {
            node_next_sibling.r()
        } else {
            child_next_sibling.r()
        };

        // Step 9.
        let document = document_from_node(self).root();
        Node::adopt(node, document.r());

        // Step 12.
        let mut nodes: RootedVec<JS<Node>> = RootedVec::new();
        if node.type_id() == NodeTypeId::DocumentFragment {
            // Collect fragment children before Step 11,
            // because Node::insert removes a DocumentFragment's children,
            // and we need them in Step 13.
            // Issue filed against the spec:
            // https://www.w3.org/Bugs/Public/show_bug.cgi?id=28330
            for child_node in node.children() {
                let child_node = child_node.root();
                nodes.push(JS::from_rooted(child_node.r()));
            }
        } else {
            nodes.push(JS::from_rooted(node));
        }

        {
            // Step 10.
            Node::remove(child, self, SuppressObserver::Suppressed);

            // Step 11.
            Node::insert(node, self, reference_child, SuppressObserver::Suppressed);
        }

        // Step 13: mutation records.
        child.node_removed(self.is_in_doc());
        for child_node in &*nodes {
            let child_node = child_node.root();
            child_node.r().node_inserted();
        }

        // Step 14.
        Ok(Temporary::from_rooted(child))
    }

    // https://dom.spec.whatwg.org/#dom-node-removechild
    fn RemoveChild(self, node: JSRef<Node>)
                       -> Fallible<Temporary<Node>> {
        Node::pre_remove(node, self)
    }

    // https://dom.spec.whatwg.org/#dom-node-normalize
    fn Normalize(self) {
        let mut prev_text: Option<Temporary<Text>> = None;
        for child in self.children() {
            let child = child.root();
            match TextCast::to_ref(child.r()) {
                Some(text) => {
                    let characterdata: JSRef<CharacterData> = CharacterDataCast::from_ref(text);
                    if characterdata.Length() == 0 {
                        self.remove_child(child.r());
                    } else {
                        match prev_text {
                            Some(ref text_node) => {
                                let text_node = text_node.clone().root();
                                let prev_characterdata: JSRef<CharacterData> = CharacterDataCast::from_ref(text_node.r());
                                let _ = prev_characterdata.AppendData(characterdata.Data());
                                self.remove_child(child.r());
                            },
                            None => prev_text = Some(Temporary::from_rooted(text))
                        }
                    }
                },
                None => {
                    child.r().Normalize();
                    prev_text = None;
                }
            }
        }
    }

    // https://dom.spec.whatwg.org/#dom-node-clonenode
    fn CloneNode(self, deep: bool) -> Temporary<Node> {
        Node::clone(self, None, if deep {
            CloneChildrenFlag::CloneChildren
        } else {
            CloneChildrenFlag::DoNotCloneChildren
        })
    }

    // https://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.name() == *other_doctype.name()) &&
            (*doctype.public_id() == *other_doctype.public_id()) &&
            (*doctype.system_id() == *other_doctype.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
            (*element.namespace() == *other_element.namespace()) &&
            (*element.local_name() == *other_element.local_name()) &&
            (element.attrs().len() == other_element.attrs().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.target() == *other_pi.target()) &&
            (*CharacterDataCast::from_ref(pi).data() == *CharacterDataCast::from_ref(other_pi).data())
        }
        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();
            // FIXME(https://github.com/rust-lang/rust/issues/23338)
            let own_data = characterdata.data();
            let other_data = other_characterdata.data();
            *own_data == *other_data
        }
        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();
            assert!(element.attrs().len() == other_element.attrs().len());
            // FIXME(https://github.com/rust-lang/rust/issues/23338)
            let attrs = element.attrs();
            attrs.iter().map(|attr| attr.root()).all(|attr| {
                other_element.attrs().iter().map(|attr| attr.root()).any(|other_attr| {
                    (*attr.r().namespace() == *other_attr.r().namespace()) &&
                    (attr.r().local_name() == other_attr.r().local_name()) &&
                    (**attr.r().value() == **other_attr.r().value())
                })
            })
        }
        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.
                NodeTypeId::DocumentType if !is_equal_doctype(this, node) => return false,
                NodeTypeId::Element(..) if !is_equal_element(this, node) => return false,
                NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) if !is_equal_processinginstruction(this, node) => return false,
                NodeTypeId::CharacterData(CharacterDataTypeId::Text) |
                NodeTypeId::CharacterData(CharacterDataTypeId::Comment) if !is_equal_characterdata(this, node) => return false,
                // Step 4.
                NodeTypeId::Element(..) 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(|(child, other_child)| {
                is_equal_node(child.root().r(), other_child.root().r())
            })
        }
        match maybe_node {
            // Step 1.
            None => false,
            // Step 2-6.
            Some(node) => is_equal_node(self, node)
        }
    }

    // https://dom.spec.whatwg.org/#dom-node-comparedocumentposition
    fn CompareDocumentPosition(self, other: JSRef<Node>) -> u16 {
        if self == other {
            // step 2.
            0
        } else {
            let mut lastself = Temporary::from_rooted(self.clone());
            let mut lastother = Temporary::from_rooted(other.clone());
            for ancestor in self.ancestors() {
                let ancestor = ancestor.root();
                if ancestor.r() == other {
                    // step 4.
                    return NodeConstants::DOCUMENT_POSITION_CONTAINS +
                           NodeConstants::DOCUMENT_POSITION_PRECEDING;
                }
                lastself = Temporary::from_rooted(ancestor.r());
            }
            for ancestor in other.ancestors() {
                let ancestor = ancestor.root();
                if ancestor.r() == self {
                    // step 5.
                    return NodeConstants::DOCUMENT_POSITION_CONTAINED_BY +
                           NodeConstants::DOCUMENT_POSITION_FOLLOWING;
                }
                lastother = Temporary::from_rooted(ancestor.r());
            }

            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;
            }

            let lastself = lastself.root();
            for child in lastself.r().traverse_preorder() {
                let child = child.root();
                if child.r() == other {
                    // step 6.
                    return NodeConstants::DOCUMENT_POSITION_PRECEDING;
                }
                if child.r() == self {
                    // step 7.
                    return NodeConstants::DOCUMENT_POSITION_FOLLOWING;
                }
            }
            unreachable!()
        }
    }

    // https://dom.spec.whatwg.org/#dom-node-contains
    fn Contains(self, maybe_other: Option<JSRef<Node>>) -> bool {
        match maybe_other {
            None => false,
            Some(other) => self.is_inclusive_ancestor_of(other)
        }
    }

    // https://dom.spec.whatwg.org/#dom-node-lookupprefix
    fn LookupPrefix(self, namespace: Option<DOMString>) -> Option<DOMString> {
        // Step 1.
        if null_str_as_empty(&namespace).is_empty() {
            return None;
        }

        // Step 2.
        match self.type_id() {
            NodeTypeId::Element(..) => ElementCast::to_ref(self).unwrap().lookup_prefix(namespace),
            NodeTypeId::Document => {
                DocumentCast::to_ref(self).unwrap().GetDocumentElement().and_then(|element| {
                    element.root().r().lookup_prefix(namespace)
                })
            },
            NodeTypeId::DocumentType | NodeTypeId::DocumentFragment => None,
            _ => {
                self.GetParentElement().and_then(|element| {
                    element.root().r().lookup_prefix(namespace)
                })
            }
        }
    }

    // https://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri
    fn LookupNamespaceURI(self, _namespace: Option<DOMString>) -> Option<DOMString> {
        // FIXME (#1826) implement.
        None
    }

    // https://dom.spec.whatwg.org/#dom-node-isdefaultnamespace
    fn IsDefaultNamespace(self, _namespace: Option<DOMString>) -> bool {
        // FIXME (#1826) implement.
        false
    }
}



/// The address of a node known to be valid. These are sent from script to layout,
/// and are also used in the HTML parser interface.

#[allow(raw_pointer_derive)]
#[derive(Clone, PartialEq, Eq, Copy)]
pub struct TrustedNodeAddress(pub *const c_void);

#[allow(unsafe_code)]
unsafe impl Send for TrustedNodeAddress {}

pub fn document_from_node<T: NodeBase+Reflectable>(derived: JSRef<T>) -> Temporary<Document> {
    let node: JSRef<Node> = NodeCast::from_ref(derived);
    node.owner_doc()
}

pub fn window_from_node<T: NodeBase+Reflectable>(derived: JSRef<T>) -> Temporary<Window> {
    let document = document_from_node(derived).root();
    document.r().window()
}

impl<'a> VirtualMethods for JSRef<'a, Node> {
    fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> {
        let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_borrowed_ref(self);
        Some(eventtarget as &VirtualMethods)
    }
}

impl<'a> style::node::TNode<'a> for JSRef<'a, Node> {
    type Element = JSRef<'a, Element>;

    fn parent_node(self) -> Option<JSRef<'a, Node>> {
        (*self).parent_node.get()
               .map(|node| node.root().get_unsound_ref_forever())
    }

    fn first_child(self) -> Option<JSRef<'a, Node>> {
        (*self).first_child.get()
               .map(|node| node.root().get_unsound_ref_forever())
    }

    fn last_child(self) -> Option<JSRef<'a, Node>> {
        (*self).last_child.get()
               .map(|node| node.root().get_unsound_ref_forever())
    }

    fn prev_sibling(self) -> Option<JSRef<'a, Node>> {
        (*self).prev_sibling.get()
               .map(|node| node.root().get_unsound_ref_forever())
    }

    fn next_sibling(self) -> Option<JSRef<'a, Node>> {
        (*self).next_sibling.get()
               .map(|node| node.root().get_unsound_ref_forever())
    }

    fn is_document(self) -> bool {
        // FIXME(zwarich): Remove this when UFCS lands and there is a better way
        // of disambiguating methods.
        fn is_document<'a, T: NodeHelpers>(this: T) -> bool {
            this.is_document()
        }

        is_document(self)
    }

    fn is_element(self) -> bool {
        // FIXME(zwarich): Remove this when UFCS lands and there is a better way
        // of disambiguating methods.
        fn is_element<'a, T: NodeHelpers>(this: T) -> bool {
            this.is_element()
        }

        is_element(self)
    }

    fn as_element(self) -> JSRef<'a, Element> {
        ElementCast::to_ref(self).unwrap()
    }

    fn match_attr<F>(self, attr: &AttrSelector, test: F) -> bool
        where F: Fn(&str) -> bool
    {
        let local_name = {
            if self.is_html_element_in_html_document() {
                &attr.lower_name
            } else {
                &attr.name
            }
        };
        match attr.namespace {
            NamespaceConstraint::Specific(ref ns) => {
                self.as_element().get_attribute(ns, local_name).root()
                    .map_or(false, |attr| {
                        // FIXME(https://github.com/rust-lang/rust/issues/23338)
                        let attr = attr.r();
                        let value = attr.value();
                        test(&value)
                    })
            },
            NamespaceConstraint::Any => {
                let mut attributes: RootedVec<JS<Attr>> = RootedVec::new();
                self.as_element().get_attributes(local_name, &mut attributes);
                attributes.iter().map(|attr| attr.root()).any(|attr| {
                        // FIXME(https://github.com/rust-lang/rust/issues/23338)
                        let attr = attr.r();
                        let value = attr.value();
                        test(&value)
                    })
            }
        }
    }

    fn is_html_element_in_html_document(self) -> bool {
        self.as_element().html_element_in_html_document()
    }

    fn has_changed(self) -> bool { self.get_has_changed() }
    #[allow(unsafe_code)]
    unsafe fn set_changed(self, value: bool) { self.set_has_changed(value) }

    fn is_dirty(self) -> bool { self.get_is_dirty() }
    #[allow(unsafe_code)]
    unsafe fn set_dirty(self, value: bool) { self.set_is_dirty(value) }

    fn has_dirty_siblings(self) -> bool { self.get_has_dirty_siblings() }
    #[allow(unsafe_code)]
    unsafe fn set_dirty_siblings(self, value: bool) { self.set_has_dirty_siblings(value) }

    fn has_dirty_descendants(self) -> bool { self.get_has_dirty_descendants() }
    #[allow(unsafe_code)]
    unsafe fn set_dirty_descendants(self, value: bool) { self.set_has_dirty_descendants(value) }
}

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() {
            let ancestor = ancestor.root();
            let ancestor = ancestor.r();
            if !ancestor.is_htmlfieldsetelement() { continue; }
            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()
                          .map(|child| child.root())
                          .find(|child| child.r().is_htmllegendelement())
            {
                Some(ref legend) => {
                    // XXXabinader: should we save previous ancestor to avoid this iteration?
                    if self.ancestors().any(|ancestor| ancestor.root().r() == legend.r()) { 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; }
        if let Some(ref parent) = self.GetParentNode().root() {
            if parent.r().is_htmloptgroupelement() && parent.r().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(&atom!("disabled"));
        self.set_disabled_state(has_disabled_attrib);
        self.set_enabled_state(!has_disabled_attrib);
    }
}

/// A summary of the changes that happened to a node.
#[derive(Copy, Clone, PartialEq)]
pub enum NodeDamage {
    /// The node's `style` attribute changed.
    NodeStyleDamaged,
    /// Other parts of a node changed; attributes, text content, etc.
    OtherNodeDamage,
}