servo/components/script/dom/node.rs
Anthony Ramine 7d6ea83479 Explicitly customise flags of new nodes where needed
Given codegen now generates the various TypeId enums, it seems pointless to
still have to write their respective values in every DOM struct inheriting from
Node just to set the initial IS_IN_DOC flag in Document and IN_ENABLED_STATE in
form controls.
2015-10-15 17:30:41 +02:00

2575 lines
88 KiB
Rust

/* 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 app_units::Au;
use core::nonzero::NonZero;
use devtools_traits::NodeInfo;
use document_loader::DocumentLoader;
use dom::attr::Attr;
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, CharacterDataTypeId};
use dom::bindings::codegen::InheritTypes::{DocumentCast, DocumentDerived, DocumentTypeCast};
use dom::bindings::codegen::InheritTypes::{ElementCast, ElementDerived, ElementTypeId};
use dom::bindings::codegen::InheritTypes::{EventTargetCast, EventTargetTypeId};
use dom::bindings::codegen::InheritTypes::{HTMLElementTypeId, HTMLFieldSetElementDerived};
use dom::bindings::codegen::InheritTypes::{HTMLLegendElementDerived, HTMLOptGroupElementDerived};
use dom::bindings::codegen::InheritTypes::{NodeBase, NodeCast, NodeTypeId};
use dom::bindings::codegen::InheritTypes::{ProcessingInstructionCast, TextCast, TextDerived};
use dom::bindings::codegen::UnionTypes::NodeOrString;
use dom::bindings::conversions;
use dom::bindings::error::{Error, ErrorResult, Fallible};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::Root;
use dom::bindings::js::RootedReference;
use dom::bindings::js::{JS, LayoutJS, MutNullableHeap};
use dom::bindings::trace::JSTraceable;
use dom::bindings::trace::RootedVec;
use dom::bindings::utils::{Reflectable, namespace_from_domstring, reflect_dom_object};
use dom::characterdata::CharacterData;
use dom::comment::Comment;
use dom::document::{Document, DocumentSource, IsHTMLDocument};
use dom::documentfragment::DocumentFragment;
use dom::documenttype::DocumentType;
use dom::element::{Element, ElementCreator};
use dom::eventtarget::EventTarget;
use dom::nodelist::NodeList;
use dom::processinginstruction::ProcessingInstruction;
use dom::text::Text;
use dom::virtualmethods::{VirtualMethods, vtable_for};
use dom::window::Window;
use euclid::rect::Rect;
use js::jsapi::{JSContext, JSObject, JSRuntime};
use layout_interface::{LayoutChan, Msg};
use libc::{self, c_void, uintptr_t};
use parse::html::parse_html_fragment;
use script_traits::UntrustedNodeAddress;
use selectors::matching::matches;
use selectors::parser::Selector;
use selectors::parser::parse_author_origin_selector_list_from_str;
use std::borrow::ToOwned;
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::default::Default;
use std::iter::{FilterMap, Peekable};
use std::mem;
use std::slice::ref_slice;
use std::sync::Arc;
use string_cache::{Atom, Namespace, QualName};
use style::properties::ComputedValues;
use util::str::DOMString;
use util::task_state;
use uuid;
//
// The basic Node structure
//
/// An HTML node.
#[dom_struct]
pub struct Node {
/// The JavaScript reflector for this node.
eventtarget: EventTarget,
/// 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>>,
/// The live count of children of this node.
children_count: Cell<u32>,
/// 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 PartialEq for Node {
fn eq(&self, other: &Node) -> bool {
self as *const Node == &*other
}
}
bitflags! {
#[doc = "Flags for node items."]
#[derive(JSTraceable, HeapSizeOf)]
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,
#[doc = "Specifies whether this node is focusable and whether it is supposed \
to be reachable with using sequential focus navigation."]
const SEQUENTIALLY_FOCUSABLE = 0x400,
#[doc = "Specifies whether this node is [being activated]\
(https://html.spec.whatwg.org/multipage/#selector-active). \
FIXME(#7333): set/unset this when appropriate"]
const IN_ACTIVE_STATE = 0x800,
}
}
impl NodeFlags {
pub fn new() -> NodeFlags {
HAS_CHANGED | IS_DIRTY | HAS_DIRTY_SIBLINGS | HAS_DIRTY_DESCENDANTS
}
}
impl Drop for Node {
#[allow(unsafe_code)]
fn drop(&mut self) {
self.layout_data.dispose(self);
}
}
/// suppress observers flag
/// https://dom.spec.whatwg.org/#concept-node-insert
/// https://dom.spec.whatwg.org/#concept-node-remove
#[derive(Copy, Clone, HeapSizeOf)]
enum SuppressObserver {
Suppressed,
Unsuppressed
}
/// Layout data that is shared between the script and layout tasks.
#[derive(HeapSizeOf)]
pub struct SharedLayoutData {
/// The results of CSS styling for this node.
pub style: Option<Arc<ComputedValues>>,
}
/// Encapsulates the abstract layout data.
#[allow(raw_pointer_derive)]
#[derive(HeapSizeOf)]
pub struct LayoutData {
_shared_data: SharedLayoutData,
#[ignore_heap_size_of = "TODO(#6910) Box value that should be counted but the type lives in layout"]
_data: NonZero<*const ()>,
}
#[allow(unsafe_code)]
unsafe impl Send for LayoutData {}
#[derive(HeapSizeOf)]
pub struct LayoutDataRef {
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, node: &Node) {
debug_assert!(task_state::get().is_script());
if let Some(layout_data) = mem::replace(&mut *self.data_cell.borrow_mut(), None) {
let win = window_from_node(node);
let LayoutChan(chan) = win.layout_chan();
chan.send(Msg::ReapLayoutData(layout_data)).unwrap()
}
}
/// Borrows the layout data immutably, *assuming 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> {
debug_assert!(task_state::get().is_layout());
self.data_cell.as_unsafe_cell().get() as *const _
}
/// Borrows the layout data immutably. This function is *not* thread-safe.
#[inline]
pub fn borrow(&self) -> Ref<Option<LayoutData>> {
debug_assert!(task_state::get().is_layout());
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(&self) -> RefMut<Option<LayoutData>> {
debug_assert!(task_state::get().is_layout());
self.data_cell.borrow_mut()
}
}
impl Node {
/// 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: &Node, before: Option<&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_rooted().r() == Some(self));
let prev_sibling = before.GetPreviousSibling();
match prev_sibling {
None => {
assert!(Some(*before) == self.first_child.get_rooted().r());
self.first_child.set(Some(JS::from_ref(new_child)));
},
Some(ref prev_sibling) => {
prev_sibling.next_sibling.set(Some(JS::from_ref(new_child)));
new_child.prev_sibling.set(Some(JS::from_ref(prev_sibling.r())));
},
}
before.prev_sibling.set(Some(JS::from_ref(new_child)));
new_child.next_sibling.set(Some(JS::from_ref(before)));
},
None => {
let last_child = self.GetLastChild();
match last_child {
None => self.first_child.set(Some(JS::from_ref(new_child))),
Some(ref last_child) => {
assert!(last_child.next_sibling.get().is_none());
last_child.r().next_sibling.set(Some(JS::from_ref(new_child)));
new_child.prev_sibling.set(Some(JS::from_rooted(&last_child)));
}
}
self.last_child.set(Some(JS::from_ref(new_child)));
},
}
new_child.parent_node.set(Some(JS::from_ref(self)));
let parent_in_doc = self.is_in_doc();
for node in new_child.traverse_preorder() {
node.set_flag(IS_IN_DOC, parent_in_doc);
vtable_for(&&*node).bind_to_tree(parent_in_doc);
}
let document = new_child.owner_doc();
document.content_and_heritage_changed(new_child, NodeDamage::OtherNodeDamage);
}
/// 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: &Node) {
assert!(child.parent_node.get_rooted().r() == Some(self));
let prev_sibling = child.GetPreviousSibling();
match prev_sibling {
None => {
self.first_child.set(child.next_sibling.get());
}
Some(ref prev_sibling) => {
prev_sibling.next_sibling.set(child.next_sibling.get());
}
}
let next_sibling = child.GetNextSibling();
match next_sibling {
None => {
self.last_child.set(child.prev_sibling.get());
}
Some(ref next_sibling) => {
next_sibling.prev_sibling.set(child.prev_sibling.get());
}
}
child.prev_sibling.set(None);
child.next_sibling.set(None);
child.parent_node.set(None);
let parent_in_doc = self.is_in_doc();
for node in child.traverse_preorder() {
node.set_flag(IS_IN_DOC, false);
vtable_for(&&*node).unbind_from_tree(parent_in_doc);
node.layout_data.dispose(&node);
}
let document = child.owner_doc();
document.content_and_heritage_changed(child, NodeDamage::OtherNodeDamage);
}
}
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 = Root<Node>;
fn next(&mut self) -> Option<Root<Node>> {
let selectors = &self.selectors;
// TODO(cgaebel): Is it worth it to build a bloom filter here
// (instead of passing `None`)? Probably.
self.iterator.by_ref().filter_map(|node| {
if let Some(element) = ElementCast::to_root(node) {
if matches(selectors, &element, None) {
return Some(NodeCast::from_root(element))
}
}
None
}).next()
}
}
impl Node {
pub fn teardown(&self) {
self.layout_data.dispose(self);
for kid in self.children() {
kid.r().teardown();
}
}
/// Dumps the subtree rooted at this node, for debugging.
pub fn dump(&self) {
self.dump_indent(0);
}
/// Dumps the node tree, for debugging, with indentation.
pub 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() {
kid.r().dump_indent(indent + 1)
}
}
/// Returns a string that describes this node.
pub fn debug_str(&self) -> String {
format!("{:?}", self.type_id())
}
pub fn is_in_doc(&self) -> bool {
self.flags.get().contains(IS_IN_DOC)
}
/// Returns the type ID of this node.
pub fn type_id(&self) -> NodeTypeId {
match *self.eventtarget.type_id() {
EventTargetTypeId::Node(type_id) => type_id,
_ => unreachable!(),
}
}
// https://dom.spec.whatwg.org/#concept-node-length
pub fn len(&self) -> u32 {
match self.type_id() {
NodeTypeId::DocumentType => 0,
NodeTypeId::CharacterData(_) => {
CharacterDataCast::to_ref(self).unwrap().Length()
},
_ => self.children_count(),
}
}
// https://dom.spec.whatwg.org/#concept-tree-index
pub fn index(&self) -> u32 {
self.preceding_siblings().count() as u32
}
pub fn children_count(&self) -> u32 {
self.children_count.get()
}
#[inline]
pub fn is_anchor_element(&self) -> bool {
self.type_id() == NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement))
}
#[inline]
pub fn is_doctype(&self) -> bool {
self.type_id() == NodeTypeId::DocumentType
}
pub fn get_flag(&self, flag: NodeFlags) -> bool {
self.flags.get().contains(flag)
}
pub 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);
}
pub fn get_hover_state(&self) -> bool {
self.get_flag(IN_HOVER_STATE)
}
pub fn set_hover_state(&self, state: bool) {
self.set_flag(IN_HOVER_STATE, state);
self.dirty(NodeDamage::NodeStyleDamaged);
}
pub fn get_focus_state(&self) -> bool {
self.get_flag(IN_FOCUS_STATE)
}
pub fn set_focus_state(&self, state: bool) {
self.set_flag(IN_FOCUS_STATE, state);
self.dirty(NodeDamage::NodeStyleDamaged);
}
pub fn get_active_state(&self) -> bool {
self.get_flag(IN_ACTIVE_STATE)
}
pub fn set_active_state(&self, state: bool) {
self.set_flag(IN_ACTIVE_STATE, state);
self.dirty(NodeDamage::NodeStyleDamaged);
}
pub fn get_disabled_state(&self) -> bool {
self.get_flag(IN_DISABLED_STATE)
}
pub fn set_disabled_state(&self, state: bool) {
self.set_flag(IN_DISABLED_STATE, state)
}
pub fn get_enabled_state(&self) -> bool {
self.get_flag(IN_ENABLED_STATE)
}
pub fn set_enabled_state(&self, state: bool) {
self.set_flag(IN_ENABLED_STATE, state)
}
pub fn get_has_changed(&self) -> bool {
self.get_flag(HAS_CHANGED)
}
pub fn set_has_changed(&self, state: bool) {
self.set_flag(HAS_CHANGED, state)
}
pub fn get_is_dirty(&self) -> bool {
self.get_flag(IS_DIRTY)
}
pub fn set_is_dirty(&self, state: bool) {
self.set_flag(IS_DIRTY, state)
}
pub fn get_has_dirty_siblings(&self) -> bool {
self.get_flag(HAS_DIRTY_SIBLINGS)
}
pub fn set_has_dirty_siblings(&self, state: bool) {
self.set_flag(HAS_DIRTY_SIBLINGS, state)
}
pub fn get_has_dirty_descendants(&self) -> bool {
self.get_flag(HAS_DIRTY_DESCENDANTS)
}
pub fn set_has_dirty_descendants(&self, state: bool) {
self.set_flag(HAS_DIRTY_DESCENDANTS, state)
}
pub fn force_dirty_ancestors(&self, damage: NodeDamage) {
self.dirty_impl(damage, true)
}
pub fn dirty(&self, damage: NodeDamage) {
self.dirty_impl(damage, false)
}
pub 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: &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() {
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() {
sibling.r().set_has_dirty_siblings(true);
}
}
// 4. Dirty ancestors.
for ancestor in self.ancestors() {
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.
pub fn traverse_preorder(&self) -> TreeIterator {
TreeIterator::new(self)
}
pub fn inclusively_following_siblings(&self) -> NodeSiblingIterator {
NodeSiblingIterator {
current: Some(Root::from_ref(self)),
}
}
pub fn inclusively_preceding_siblings(&self) -> ReverseSiblingIterator {
ReverseSiblingIterator {
current: Some(Root::from_ref(self)),
}
}
pub fn is_inclusive_ancestor_of(&self, parent: &Node) -> bool {
self == parent || parent.ancestors().any(|ancestor| ancestor.r() == self)
}
pub fn following_siblings(&self) -> NodeSiblingIterator {
NodeSiblingIterator {
current: self.GetNextSibling(),
}
}
pub fn preceding_siblings(&self) -> ReverseSiblingIterator {
ReverseSiblingIterator {
current: self.GetPreviousSibling(),
}
}
pub fn following_nodes(&self, root: &Node) -> FollowingNodeIterator {
FollowingNodeIterator {
current: Some(Root::from_ref(self)),
root: Root::from_ref(root),
}
}
pub fn preceding_nodes(&self, root: &Node) -> PrecedingNodeIterator {
PrecedingNodeIterator {
current: Some(Root::from_ref(self)),
root: Root::from_ref(root),
}
}
pub fn descending_last_children(&self) -> LastChildIterator {
LastChildIterator {
current: self.GetLastChild(),
}
}
pub fn is_parent_of(&self, child: &Node) -> bool {
match child.parent_node.get() {
Some(ref parent) => parent.root().r() == self,
None => false,
}
}
pub fn to_trusted_node_address(&self) -> TrustedNodeAddress {
TrustedNodeAddress(&*self as *const Node as *const libc::c_void)
}
pub fn get_bounding_content_box(&self) -> Rect<Au> {
window_from_node(self).r().content_box_query(self.to_trusted_node_address())
}
pub fn get_content_boxes(&self) -> Vec<Rect<Au>> {
window_from_node(self).r().content_boxes_query(self.to_trusted_node_address())
}
pub fn get_client_rect(&self) -> Rect<i32> {
window_from_node(self).r().client_rect_query(self.to_trusted_node_address())
}
// https://dom.spec.whatwg.org/#dom-childnode-before
pub fn before(&self, nodes: Vec<NodeOrString>) -> ErrorResult {
// Step 1.
let parent = &self.parent_node;
// Step 2.
let parent = match parent.get() {
None => return Ok(()),
Some(ref parent) => parent.root(),
};
// Step 3.
let viable_previous_sibling = first_node_not_in(self.preceding_siblings(), &nodes);
// Step 4.
let node = try!(self.owner_doc().node_from_nodes_and_strings(nodes));
// Step 5.
let viable_previous_sibling = match viable_previous_sibling {
Some(ref viable_previous_sibling) => viable_previous_sibling.next_sibling.get(),
None => parent.first_child.get(),
}.map(|s| s.root());
// Step 6.
try!(Node::pre_insert(&node, &parent, viable_previous_sibling.r()));
Ok(())
}
// https://dom.spec.whatwg.org/#dom-childnode-after
pub fn after(&self, nodes: Vec<NodeOrString>) -> ErrorResult {
// Step 1.
let parent = &self.parent_node;
// Step 2.
let parent = match parent.get() {
None => return Ok(()),
Some(ref parent) => parent.root(),
};
// Step 3.
let viable_next_sibling = first_node_not_in(self.following_siblings(), &nodes);
// Step 4.
let node = try!(self.owner_doc().node_from_nodes_and_strings(nodes));
// Step 5.
try!(Node::pre_insert(&node, &parent, viable_next_sibling.r()));
Ok(())
}
// https://dom.spec.whatwg.org/#dom-childnode-replacewith
pub fn replace_with(&self, nodes: Vec<NodeOrString>) -> ErrorResult {
match self.parent_node.get() {
None => {
// Step 1.
Ok(())
},
Some(ref parent_node) => {
// Step 2.
let doc = self.owner_doc();
let node = try!(doc.r().node_from_nodes_and_strings(nodes));
// Step 3.
parent_node.root().r().ReplaceChild(node.r(), self).map(|_| ())
},
}
}
// https://dom.spec.whatwg.org/#dom-parentnode-prepend
pub fn prepend(&self, nodes: Vec<NodeOrString>) -> ErrorResult {
// Step 1.
let doc = self.owner_doc();
let node = try!(doc.r().node_from_nodes_and_strings(nodes));
// Step 2.
let first_child = self.first_child.get_rooted();
Node::pre_insert(node.r(), self, first_child.r()).map(|_| ())
}
// https://dom.spec.whatwg.org/#dom-parentnode-append
pub fn append(&self, nodes: Vec<NodeOrString>) -> ErrorResult {
// Step 1.
let doc = self.owner_doc();
let node = try!(doc.r().node_from_nodes_and_strings(nodes));
// Step 2.
self.AppendChild(node.r()).map(|_| ())
}
// https://dom.spec.whatwg.org/#dom-parentnode-queryselector
pub fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
// Step 1.
match parse_author_origin_selector_list_from_str(&selectors) {
// Step 2.
Err(()) => Err(Error::Syntax),
// Step 3.
Ok(ref selectors) => {
Ok(self.traverse_preorder().filter_map(ElementCast::to_root).find(|element| {
matches(selectors, element, None)
}))
}
}
}
/// https://dom.spec.whatwg.org/#scope-match-a-selectors-string
/// 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)]
pub 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(Error::Syntax),
// Step 3.
Ok(selectors) => {
Ok(QuerySelectorIterator::new(self.traverse_preorder(), selectors))
}
}
}
// https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
#[allow(unsafe_code)]
pub fn query_selector_all(&self, selectors: DOMString) -> Fallible<Root<NodeList>> {
let window = window_from_node(self);
let iter = try!(unsafe { self.query_selector_iter(selectors) });
Ok(NodeList::new_simple_list(window.r(), iter))
}
pub fn ancestors(&self) -> AncestorIterator {
AncestorIterator {
current: self.GetParentNode()
}
}
pub fn inclusive_ancestors(&self) -> AncestorIterator {
AncestorIterator {
current: Some(Root::from_ref(self))
}
}
pub fn owner_doc(&self) -> Root<Document> {
self.owner_doc.get().unwrap().root()
}
pub fn set_owner_doc(&self, document: &Document) {
self.owner_doc.set(Some(JS::from_ref(document)));
}
pub fn is_in_html_doc(&self) -> bool {
self.owner_doc().r().is_html_document()
}
pub fn children(&self) -> NodeSiblingIterator {
NodeSiblingIterator {
current: self.GetFirstChild(),
}
}
pub fn rev_children(&self) -> ReverseSiblingIterator {
ReverseSiblingIterator {
current: self.GetLastChild(),
}
}
pub fn child_elements(&self) -> ChildElementIterator {
fn to_temporary(node: Root<Node>) -> Option<Root<Element>> {
ElementCast::to_root(node)
}
self.children()
.filter_map(to_temporary as fn(_) -> _)
.peekable()
}
pub fn remove_self(&self) {
if let Some(ref parent) = self.GetParentNode() {
Node::remove(self, parent.r(), SuppressObserver::Unsuppressed);
}
}
pub fn get_unique_id(&self) -> String {
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();
}
self.unique_id.borrow().clone()
}
pub fn summarize(&self) -> NodeInfo {
NodeInfo {
uniqueId: self.get_unique_id(),
baseURI: self.BaseURI(),
parent: self.GetParentNode().map(|node| node.r().get_unique_id()).unwrap_or("".to_owned()),
nodeType: self.NodeType(),
namespaceURI: "".to_owned(), //FIXME
nodeName: self.NodeName(),
numChildren: self.ChildNodes().r().Length() as usize,
//FIXME doctype nodes only
name: "".to_owned(),
publicId: "".to_owned(),
systemId: "".to_owned(),
attrs: {
let e: Option<&Element> = ElementCast::to_ref(self);
match e {
Some(element) => element.summarize(),
None => vec!(),
}
},
isDocumentElement:
self.owner_doc()
.r()
.GetDocumentElement()
.map(|elem| NodeCast::from_ref(elem.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
pub fn parse_fragment(&self, markup: DOMString) -> Fallible<Root<DocumentFragment>> {
let context_node: &Node = NodeCast::from_ref(self);
let context_document = document_from_node(self);
let fragment = DocumentFragment::new(context_document.r());
if context_document.r().is_html_document() {
let fragment_node = NodeCast::from_ref(fragment.r());
parse_html_fragment(context_node, markup, fragment_node);
} else {
// FIXME: XML case
unimplemented!();
}
Ok(fragment)
}
}
/// Iterate through `nodes` until we find a `Node` that is not in `not_in`
fn first_node_not_in<I>(mut nodes: I, not_in: &[NodeOrString]) -> Option<Root<Node>>
where I: Iterator<Item=Root<Node>>
{
nodes.find(|node| {
not_in.iter().all(|n| {
match n {
&NodeOrString::eNode(ref n) => n != node,
_ => true,
}
})
})
}
/// 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)
-> Root<Node> {
unsafe {
// https://github.com/servo/servo/issues/6383
let candidate: uintptr_t = mem::transmute(candidate.0);
// let object: *mut JSObject = jsfriendapi::bindgen::JS_GetAddressableObject(runtime,
// candidate);
let object: *mut JSObject = mem::transmute(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);
Root::from_ref(&*boxed_node)
}
}
#[allow(unsafe_code)]
pub trait LayoutNodeHelpers {
unsafe fn type_id_for_layout(&self) -> NodeTypeId;
unsafe fn parent_node_ref(&self) -> Option<LayoutJS<Node>>;
unsafe fn first_child_ref(&self) -> Option<LayoutJS<Node>>;
unsafe fn last_child_ref(&self) -> Option<LayoutJS<Node>>;
unsafe fn prev_sibling_ref(&self) -> Option<LayoutJS<Node>>;
unsafe fn next_sibling_ref(&self) -> Option<LayoutJS<Node>>;
unsafe fn owner_doc_for_layout(&self) -> LayoutJS<Document>;
unsafe fn is_element_for_layout(&self) -> bool;
unsafe fn get_flag(&self, flag: NodeFlags) -> bool;
unsafe fn set_flag(&self, flag: NodeFlags, value: bool);
unsafe fn children_count(&self) -> u32;
unsafe fn layout_data(&self) -> Ref<Option<LayoutData>>;
unsafe fn layout_data_mut(&self) -> RefMut<Option<LayoutData>>;
unsafe fn layout_data_unchecked(&self) -> *const Option<LayoutData>;
fn get_hover_state_for_layout(&self) -> bool;
fn get_focus_state_for_layout(&self) -> bool;
fn get_active_state_for_layout(&self) -> bool;
fn get_disabled_state_for_layout(&self) -> bool;
fn get_enabled_state_for_layout(&self) -> 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);
}
#[inline]
#[allow(unsafe_code)]
unsafe fn children_count(&self) -> u32 {
(*self.unsafe_get()).children_count.get()
}
#[inline]
#[allow(unsafe_code)]
unsafe fn layout_data(&self) -> Ref<Option<LayoutData>> {
(*self.unsafe_get()).layout_data.borrow()
}
#[inline]
#[allow(unsafe_code)]
unsafe fn layout_data_mut(&self) -> RefMut<Option<LayoutData>> {
(*self.unsafe_get()).layout_data.borrow_mut()
}
#[inline]
#[allow(unsafe_code)]
unsafe fn layout_data_unchecked(&self) -> *const Option<LayoutData> {
(*self.unsafe_get()).layout_data.borrow_unchecked()
}
#[inline]
#[allow(unsafe_code)]
fn get_hover_state_for_layout(&self) -> bool {
unsafe {
self.get_flag(IN_HOVER_STATE)
}
}
#[inline]
#[allow(unsafe_code)]
fn get_focus_state_for_layout(&self) -> bool {
unsafe {
self.get_flag(IN_FOCUS_STATE)
}
}
#[inline]
#[allow(unsafe_code)]
fn get_active_state_for_layout(&self) -> bool {
unsafe {
self.get_flag(IN_ACTIVE_STATE)
}
}
#[inline]
#[allow(unsafe_code)]
fn get_disabled_state_for_layout(&self) -> bool {
unsafe {
self.get_flag(IN_DISABLED_STATE)
}
}
#[inline]
#[allow(unsafe_code)]
fn get_enabled_state_for_layout(&self) -> bool {
unsafe {
self.get_flag(IN_ENABLED_STATE)
}
}
}
//
// Iteration and traversal
//
pub type ChildElementIterator =
Peekable<FilterMap<NodeSiblingIterator,
fn(Root<Node>) -> Option<Root<Element>>>>;
pub struct NodeSiblingIterator {
current: Option<Root<Node>>,
}
impl Iterator for NodeSiblingIterator {
type Item = Root<Node>;
fn next(&mut self) -> Option<Root<Node>> {
let current = match self.current.take() {
None => return None,
Some(current) => current,
};
self.current = current.r().GetNextSibling();
Some(current)
}
}
pub struct ReverseSiblingIterator {
current: Option<Root<Node>>,
}
impl Iterator for ReverseSiblingIterator {
type Item = Root<Node>;
fn next(&mut self) -> Option<Root<Node>> {
let current = match self.current.take() {
None => return None,
Some(current) => current,
};
self.current = current.r().GetPreviousSibling();
Some(current)
}
}
pub struct FollowingNodeIterator {
current: Option<Root<Node>>,
root: Root<Node>,
}
impl Iterator for FollowingNodeIterator {
type Item = Root<Node>;
// https://dom.spec.whatwg.org/#concept-tree-following
fn next(&mut self) -> Option<Root<Node>> {
let current = match self.current.take() {
None => return None,
Some(current) => current,
};
if let Some(first_child) = current.r().GetFirstChild() {
self.current = Some(first_child);
return current.r().GetFirstChild()
}
if self.root == current {
self.current = None;
return None;
}
if let Some(next_sibling) = current.r().GetNextSibling() {
self.current = Some(next_sibling);
return current.r().GetNextSibling()
}
for ancestor in current.r().inclusive_ancestors() {
if self.root == ancestor {
break;
}
if let Some(next_sibling) = ancestor.r().GetNextSibling() {
self.current = Some(next_sibling);
return ancestor.r().GetNextSibling()
}
}
self.current = None;
None
}
}
pub struct PrecedingNodeIterator {
current: Option<Root<Node>>,
root: Root<Node>,
}
impl Iterator for PrecedingNodeIterator {
type Item = Root<Node>;
// https://dom.spec.whatwg.org/#concept-tree-preceding
fn next(&mut self) -> Option<Root<Node>> {
let current = match self.current.take() {
None => return None,
Some(current) => current,
};
if self.root == current {
self.current = None;
return None
}
let node = current;
if let Some(previous_sibling) = node.r().GetPreviousSibling() {
if self.root == previous_sibling {
self.current = None;
return None
}
if let Some(last_child) = previous_sibling.r().descending_last_children().last() {
self.current = Some(last_child);
return previous_sibling.r().descending_last_children().last()
}
self.current = Some(previous_sibling);
return node.r().GetPreviousSibling()
};
if let Some(parent_node) = node.r().GetParentNode() {
self.current = Some(parent_node);
return node.r().GetParentNode()
}
self.current = None;
None
}
}
pub struct LastChildIterator {
current: Option<Root<Node>>,
}
impl Iterator for LastChildIterator {
type Item = Root<Node>;
fn next(&mut self) -> Option<Root<Node>> {
let current = match self.current.take() {
None => return None,
Some(current) => current,
};
self.current = current.r().GetLastChild();
Some(current)
}
}
pub struct AncestorIterator {
current: Option<Root<Node>>,
}
impl Iterator for AncestorIterator {
type Item = Root<Node>;
fn next(&mut self) -> Option<Root<Node>> {
let current = match self.current.take() {
None => return None,
Some(current) => current,
};
self.current = current.r().GetParentNode();
Some(current)
}
}
pub struct TreeIterator {
current: Option<Root<Node>>,
depth: usize,
}
impl TreeIterator {
fn new(root: &Node) -> TreeIterator {
TreeIterator {
current: Some(Root::from_ref(root)),
depth: 0,
}
}
}
impl Iterator for TreeIterator {
type Item = Root<Node>;
// https://dom.spec.whatwg.org/#concept-tree-order
fn next(&mut self) -> Option<Root<Node>> {
let current = match self.current.take() {
None => return None,
Some(current) => current,
};
if let Some(first_child) = current.r().GetFirstChild() {
self.current = Some(first_child);
self.depth += 1;
return Some(current);
};
for ancestor in current.r().inclusive_ancestors() {
if self.depth == 0 {
break;
}
if let Some(next_sibling) = ancestor.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, HeapSizeOf)]
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: &Document,
wrap_fn: extern "Rust" fn(*mut JSContext, GlobalRef, Box<N>) -> Root<N>)
-> Root<N> {
let window = document.window();
reflect_dom_object(node, GlobalRef::Window(window.r()), wrap_fn)
}
pub fn new_inherited(doc: &Document) -> Node {
Node::new_inherited_with_flags(NodeFlags::new(), doc)
}
pub fn new_inherited_with_flags(flags: NodeFlags, doc: &Document) -> Node {
Node::new_(flags, Some(doc))
}
pub fn new_document_node() -> Node {
Node::new_(NodeFlags::new() | IS_IN_DOC, None)
}
fn new_(flags: NodeFlags, doc: Option<&Document>) -> Node {
Node {
eventtarget: EventTarget::new_inherited(),
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_ref)),
child_list: Default::default(),
children_count: Cell::new(0u32),
flags: Cell::new(flags),
layout_data: LayoutDataRef::new(),
unique_id: DOMRefCell::new(String::new()),
}
}
// https://dom.spec.whatwg.org/#concept-node-adopt
pub fn adopt(node: &Node, document: &Document) {
// Step 1.
let old_doc = node.owner_doc();
// Step 2.
node.remove_self();
if &*old_doc != document {
// Step 3.
for descendant in node.traverse_preorder() {
descendant.set_owner_doc(document);
}
// Step 4.
for descendant in node.traverse_preorder() {
vtable_for(&descendant).adopting_steps(&old_doc);
}
}
}
// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
pub fn ensure_pre_insertion_validity(node: &Node,
parent: &Node,
child: Option<&Node>) -> ErrorResult {
// Step 1.
match parent.type_id() {
NodeTypeId::Document |
NodeTypeId::DocumentFragment |
NodeTypeId::Element(..) => (),
_ => return Err(Error::HierarchyRequest)
}
// Step 2.
if node.is_inclusive_ancestor_of(parent) {
return Err(Error::HierarchyRequest);
}
// Step 3.
if let Some(child) = child {
if !parent.is_parent_of(child) {
return Err(Error::NotFound);
}
}
// Step 4-5.
match node.type_id() {
NodeTypeId::CharacterData(CharacterDataTypeId::Text) => {
if parent.is_document() {
return Err(Error::HierarchyRequest);
}
},
NodeTypeId::DocumentType => {
if !parent.is_document() {
return Err(Error::HierarchyRequest);
}
},
NodeTypeId::DocumentFragment |
NodeTypeId::Element(_) |
NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) |
NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => (),
NodeTypeId::Document => return Err(Error::HierarchyRequest)
}
// Step 6.
if parent.type_id() == NodeTypeId::Document {
match node.type_id() {
// Step 6.1
NodeTypeId::DocumentFragment => {
// Step 6.1.1(b)
if node.children()
.any(|c| c.r().is_text())
{
return Err(Error::HierarchyRequest);
}
match node.child_elements().count() {
0 => (),
// Step 6.1.2
1 => {
if !parent.child_elements().is_empty() {
return Err(Error::HierarchyRequest);
}
if let Some(child) = child {
if child.inclusively_following_siblings()
.any(|child| child.r().is_doctype()) {
return Err(Error::HierarchyRequest);
}
}
},
// Step 6.1.1(a)
_ => return Err(Error::HierarchyRequest),
}
},
// Step 6.2
NodeTypeId::Element(_) => {
if !parent.child_elements().is_empty() {
return Err(Error::HierarchyRequest);
}
if let Some(ref child) = child {
if child.inclusively_following_siblings()
.any(|child| child.r().is_doctype()) {
return Err(Error::HierarchyRequest);
}
}
},
// Step 6.3
NodeTypeId::DocumentType => {
if parent.children()
.any(|c| c.r().is_doctype())
{
return Err(Error::HierarchyRequest);
}
match child {
Some(child) => {
if parent.children()
.take_while(|c| c.r() != child)
.any(|c| c.r().is_element())
{
return Err(Error::HierarchyRequest);
}
},
None => {
if !parent.child_elements().is_empty() {
return Err(Error::HierarchyRequest);
}
},
}
},
NodeTypeId::CharacterData(_) => (),
NodeTypeId::Document => unreachable!(),
}
}
Ok(())
}
// https://dom.spec.whatwg.org/#concept-node-pre-insert
pub fn pre_insert(node: &Node, parent: &Node, child: Option<&Node>)
-> Fallible<Root<Node>> {
// Step 1.
try!(Node::ensure_pre_insertion_validity(node, parent, child));
// Steps 2-3.
let reference_child_root;
let reference_child = match child {
Some(child) if child == node => {
reference_child_root = node.GetNextSibling();
reference_child_root.r()
},
_ => child
};
// Step 4.
let document = document_from_node(parent);
Node::adopt(node, document.r());
// Step 5.
Node::insert(node, parent, reference_child, SuppressObserver::Unsuppressed);
// Step 6.
Ok(Root::from_ref(node))
}
// https://dom.spec.whatwg.org/#concept-node-insert
fn insert(node: &Node,
parent: &Node,
child: Option<&Node>,
suppress_observers: SuppressObserver) {
debug_assert!(&*node.owner_doc() == &*parent.owner_doc());
debug_assert!(child.map_or(true, |child| Some(parent) == child.GetParentNode().r()));
// Steps 1-2: ranges.
let mut new_nodes = RootedVec::new();
let new_nodes = if let NodeTypeId::DocumentFragment = node.type_id() {
// Step 3.
new_nodes.extend(node.children().map(|kid| JS::from_rooted(&kid)));
// Step 4: mutation observers.
// Step 5.
for kid in new_nodes.r() {
Node::remove(*kid, node, SuppressObserver::Suppressed);
}
vtable_for(&node).children_changed(&ChildrenMutation::replace_all(new_nodes.r(), &[]));
new_nodes.r()
} else {
// Step 3.
ref_slice(&node)
};
// Step 6: mutation observers.
let previous_sibling = match suppress_observers {
SuppressObserver::Unsuppressed => {
match child {
Some(child) => child.GetPreviousSibling(),
None => parent.GetLastChild(),
}
},
SuppressObserver::Suppressed => None,
};
// Step 7.
for kid in new_nodes {
// Step 7.1.
parent.add_child(*kid, child);
// Step 7.2: insertion steps.
}
if let SuppressObserver::Unsuppressed = suppress_observers {
vtable_for(&parent).children_changed(
&ChildrenMutation::insert(previous_sibling.r(), new_nodes, child));
}
}
// https://dom.spec.whatwg.org/#concept-node-replace-all
pub fn replace_all(node: Option<&Node>, parent: &Node) {
// Step 1.
if let Some(node) = node {
Node::adopt(node, &*parent.owner_doc());
}
// Step 2.
let removed_nodes = parent.children().collect::<RootedVec<_>>();
// Step 3.
let mut added_nodes = RootedVec::new();
let added_nodes = if let Some(node) = node.as_ref() {
if let NodeTypeId::DocumentFragment = node.type_id() {
added_nodes.extend(node.children().map(|child| JS::from_rooted(&child)));
added_nodes.r()
} else {
ref_slice(node)
}
} else {
&[] as &[&Node]
};
// Step 4.
for child in removed_nodes.r() {
Node::remove(*child, parent, SuppressObserver::Suppressed);
}
// Step 5.
if let Some(node) = node {
Node::insert(node, parent, None, SuppressObserver::Suppressed);
}
// Step 6: mutation observers.
vtable_for(&parent).children_changed(
&ChildrenMutation::replace_all(removed_nodes.r(), added_nodes));
}
// https://dom.spec.whatwg.org/#concept-node-pre-remove
fn pre_remove(child: &Node, parent: &Node) -> Fallible<Root<Node>> {
// Step 1.
match child.GetParentNode() {
Some(ref node) if node.r() != parent => return Err(Error::NotFound),
None => return Err(Error::NotFound),
_ => ()
}
// Step 2.
Node::remove(child, parent, SuppressObserver::Unsuppressed);
// Step 3.
Ok(Root::from_ref(child))
}
// https://dom.spec.whatwg.org/#concept-node-remove
fn remove(node: &Node, parent: &Node, suppress_observers: SuppressObserver) {
assert!(node.GetParentNode().map_or(false, |node_parent| node_parent.r() == parent));
// Step 1-5: ranges.
// Step 6.
let old_previous_sibling = node.GetPreviousSibling();
// Steps 7-8: mutation observers.
// Step 9.
let old_next_sibling = node.GetNextSibling();
parent.remove_child(node);
if let SuppressObserver::Unsuppressed = suppress_observers {
vtable_for(&parent).children_changed(
&ChildrenMutation::replace(old_previous_sibling.r(),
&node, &[],
old_next_sibling.r()));
}
}
// https://dom.spec.whatwg.org/#concept-node-clone
pub fn clone(node: &Node, maybe_doc: Option<&Document>,
clone_children: CloneChildrenFlag) -> Root<Node> {
// Step 1.
let document = match maybe_doc {
Some(doc) => Root::from_ref(doc),
None => node.owner_doc()
};
// Step 2.
// XXXabinader: clone() for each node as trait?
let copy: Root<Node> = match node.type_id() {
NodeTypeId::DocumentType => {
let doctype: &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_root(doctype)
},
NodeTypeId::DocumentFragment => {
let doc_fragment = DocumentFragment::new(document.r());
NodeCast::from_root(doc_fragment)
},
NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => {
let cdata = CharacterDataCast::to_ref(node).unwrap();
let comment = Comment::new(cdata.Data(), document.r());
NodeCast::from_root(comment)
},
NodeTypeId::Document => {
let 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();
let loader = DocumentLoader::new(&*document.loader());
let document = Document::new(window.r(), Some((*document.url()).clone()),
is_html_doc, None,
None, DocumentSource::NotFromParser, loader);
NodeCast::from_root(document)
},
NodeTypeId::Element(..) => {
let 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_root(element)
},
NodeTypeId::CharacterData(CharacterDataTypeId::Text) => {
let cdata = CharacterDataCast::to_ref(node).unwrap();
let text = Text::new(cdata.Data(), document.r());
NodeCast::from_root(text)
},
NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
let pi = ProcessingInstructionCast::to_ref(node).unwrap();
let pi = ProcessingInstruction::new(pi.Target(),
CharacterDataCast::from_ref(pi).Data(), document.r());
NodeCast::from_root(pi)
},
};
// Step 3.
let document = match DocumentCast::to_ref(copy.r()) {
Some(doc) => Root::from_ref(doc),
None => Root::from_ref(document.r()),
};
assert!(copy.r().owner_doc() == document);
// Step 4 (some data already copied in step 2).
match node.type_id() {
NodeTypeId::Document => {
let node_doc = DocumentCast::to_ref(node).unwrap();
let copy_doc = 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 = ElementCast::to_ref(node).unwrap();
let copy_elem = ElementCast::to_ref(copy.r()).unwrap();
for attr in node_elem.attrs().iter().map(JS::root) {
copy_elem.push_new_attribute(attr.local_name().clone(),
attr.value().clone(),
attr.name().clone(),
attr.namespace().clone(),
attr.prefix().clone());
}
},
_ => ()
}
// 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_copy = Node::clone(child.r(), Some(document.r()),
clone_children);
let _inserted_node = Node::pre_insert(child_copy.r(), copy.r(), None);
}
}
// Step 7.
copy
}
pub fn collect_text_contents<T: Iterator<Item=Root<Node>>>(iterator: T) -> String {
let mut content = String::new();
for node in iterator {
let text = TextCast::to_ref(node.r());
match text {
Some(text) => content.push_str(&CharacterDataCast::from_ref(text).Data()),
None => (),
}
}
content
}
pub fn namespace_to_string(namespace: Namespace) -> Option<DOMString> {
match namespace {
ns!("") => None,
Namespace(ref ns) => Some((**ns).to_owned())
}
}
// https://dom.spec.whatwg.org/#locate-a-namespace
pub fn locate_namespace(node: &Node, prefix: Option<DOMString>) -> Namespace {
fn attr_defines_namespace(attr: &Attr,
prefix: &Option<Atom>) -> bool {
*attr.namespace() == ns!(XMLNS) &&
match (attr.prefix(), prefix) {
(&Some(ref attr_prefix), &Some(ref prefix)) =>
attr_prefix == &atom!("xmlns") &&
attr.local_name() == prefix,
(&None, &None) => *attr.local_name() == atom!("xmlns"),
_ => false
}
}
match node.type_id() {
NodeTypeId::Element(_) => {
let element = ElementCast::to_ref(node).unwrap();
// Step 1.
if *element.namespace() != ns!("") && *element.prefix() == prefix {
return element.namespace().clone()
}
let prefix_atom = prefix.as_ref().map(|s| Atom::from_slice(s));
// Step 2.
let namespace_attr =
element.attrs()
.iter()
.map(|attr| attr.root())
.find(|attr| attr_defines_namespace(attr.r(),
&prefix_atom));
// Steps 2.1-2.
if let Some(attr) = namespace_attr {
return namespace_from_domstring(Some(attr.Value()));
}
match node.GetParentElement() {
// Step 3.
None => ns!(""),
// Step 4.
Some(parent) => Node::locate_namespace(NodeCast::from_ref(parent.r()), prefix)
}
},
NodeTypeId::Document => {
match DocumentCast::to_ref(node).unwrap().GetDocumentElement().r() {
// Step 1.
None => ns!(""),
// Step 2.
Some(document_element) => {
Node::locate_namespace(NodeCast::from_ref(document_element), prefix)
}
}
},
NodeTypeId::DocumentType => ns!(""),
NodeTypeId::DocumentFragment => ns!(""),
_ => match node.GetParentElement() {
// Step 1.
None => ns!(""),
// Step 2.
Some(parent) => Node::locate_namespace(NodeCast::from_ref(parent.r()), prefix)
}
}
}
}
impl NodeMethods for 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: &Element = ElementCast::to_ref(self).unwrap();
elem.TagName()
}
NodeTypeId::CharacterData(CharacterDataTypeId::Text) => "#text".to_owned(),
NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
let processing_instruction: &ProcessingInstruction =
ProcessingInstructionCast::to_ref(self).unwrap();
processing_instruction.Target()
}
NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => "#comment".to_owned(),
NodeTypeId::DocumentType => {
let doctype: &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 BaseURI(&self) -> DOMString {
self.owner_doc().URL()
}
// https://dom.spec.whatwg.org/#dom-node-ownerdocument
fn GetOwnerDocument(&self) -> Option<Root<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<Root<Node>> {
self.parent_node.get_rooted()
}
// https://dom.spec.whatwg.org/#dom-node-parentelement
fn GetParentElement(&self) -> Option<Root<Element>> {
self.GetParentNode().and_then(ElementCast::to_root)
}
// 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) -> Root<NodeList> {
self.child_list.or_init(|| {
let doc = self.owner_doc();
let window = doc.r().window();
NodeList::new_child_list(window.r(), self)
})
}
// https://dom.spec.whatwg.org/#dom-node-firstchild
fn GetFirstChild(&self) -> Option<Root<Node>> {
self.first_child.get_rooted()
}
// https://dom.spec.whatwg.org/#dom-node-lastchild
fn GetLastChild(&self) -> Option<Root<Node>> {
self.last_child.get_rooted()
}
// https://dom.spec.whatwg.org/#dom-node-previoussibling
fn GetPreviousSibling(&self) -> Option<Root<Node>> {
self.prev_sibling.get_rooted()
}
// https://dom.spec.whatwg.org/#dom-node-nextsibling
fn GetNextSibling(&self) -> Option<Root<Node>> {
self.next_sibling.get_rooted()
}
// https://dom.spec.whatwg.org/#dom-node-nodevalue
fn GetNodeValue(&self) -> Option<DOMString> {
match self.type_id() {
NodeTypeId::CharacterData(..) => {
let chardata: &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: &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 = value.unwrap_or(String::new());
match self.type_id() {
NodeTypeId::DocumentFragment |
NodeTypeId::Element(..) => {
// Step 1-2.
let node = if value.is_empty() {
None
} else {
let document = self.owner_doc();
Some(NodeCast::from_root(document.r().CreateTextNode(value)))
};
// Step 3.
Node::replace_all(node.r(), self);
}
NodeTypeId::CharacterData(..) => {
let characterdata: &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();
document.r().content_changed(self, NodeDamage::OtherNodeDamage);
}
NodeTypeId::DocumentType |
NodeTypeId::Document => {}
}
}
// https://dom.spec.whatwg.org/#dom-node-insertbefore
fn InsertBefore(&self, node: &Node, child: Option<&Node>) -> Fallible<Root<Node>> {
Node::pre_insert(node, self, child)
}
// https://dom.spec.whatwg.org/#dom-node-appendchild
fn AppendChild(&self, node: &Node) -> Fallible<Root<Node>> {
Node::pre_insert(node, self, None)
}
// https://dom.spec.whatwg.org/#concept-node-replace
fn ReplaceChild(&self, node: &Node, child: &Node) -> Fallible<Root<Node>> {
// Step 1.
match self.type_id() {
NodeTypeId::Document |
NodeTypeId::DocumentFragment |
NodeTypeId::Element(..) => (),
_ => return Err(Error::HierarchyRequest)
}
// Step 2.
if node.is_inclusive_ancestor_of(self) {
return Err(Error::HierarchyRequest);
}
// Step 3.
if !self.is_parent_of(child) {
return Err(Error::NotFound);
}
// Step 4-5.
match node.type_id() {
NodeTypeId::CharacterData(CharacterDataTypeId::Text) if self.is_document() =>
return Err(Error::HierarchyRequest),
NodeTypeId::DocumentType if !self.is_document() => return Err(Error::HierarchyRequest),
NodeTypeId::Document => return Err(Error::HierarchyRequest),
_ => ()
}
// Step 6.
if self.is_document() {
match node.type_id() {
// Step 6.1
NodeTypeId::DocumentFragment => {
// Step 6.1.1(b)
if node.children()
.any(|c| c.is_text())
{
return Err(Error::HierarchyRequest);
}
match node.child_elements().count() {
0 => (),
// Step 6.1.2
1 => {
if self.child_elements()
.any(|c| NodeCast::from_ref(c.r()) != child) {
return Err(Error::HierarchyRequest);
}
if child.following_siblings()
.any(|child| child.is_doctype()) {
return Err(Error::HierarchyRequest);
}
},
// Step 6.1.1(a)
_ => return Err(Error::HierarchyRequest)
}
},
// Step 6.2
NodeTypeId::Element(..) => {
if self.child_elements()
.any(|c| NodeCast::from_ref(c.r()) != child) {
return Err(Error::HierarchyRequest);
}
if child.following_siblings()
.any(|child| child.is_doctype())
{
return Err(Error::HierarchyRequest);
}
},
// Step 6.3
NodeTypeId::DocumentType => {
if self.children()
.any(|c| c.is_doctype() &&
c.r() != child)
{
return Err(Error::HierarchyRequest);
}
if self.children()
.take_while(|c| c.r() != child)
.any(|c| c.is_element())
{
return Err(Error::HierarchyRequest);
}
},
NodeTypeId::CharacterData(..) => (),
NodeTypeId::Document => unreachable!(),
}
}
// Ok if not caught by previous error checks.
if node == child {
return Ok(Root::from_ref(child));
}
// Step 7-8.
let child_next_sibling = child.GetNextSibling();
let node_next_sibling = node.GetNextSibling();
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);
Node::adopt(node, document.r());
// Step 10.
let previous_sibling = child.GetPreviousSibling();
// Step 11.
Node::remove(child, self, SuppressObserver::Suppressed);
// Step 12.
let mut nodes = RootedVec::new();
let nodes = if node.type_id() == NodeTypeId::DocumentFragment {
nodes.extend(node.children().map(|node| JS::from_rooted(&node)));
nodes.r()
} else {
ref_slice(&node)
};
// Step 13.
Node::insert(node, self, reference_child, SuppressObserver::Suppressed);
// Step 14.
vtable_for(&self).children_changed(
&ChildrenMutation::replace(previous_sibling.r(),
&child, nodes,
reference_child));
// Step 15.
Ok(Root::from_ref(child))
}
// https://dom.spec.whatwg.org/#dom-node-removechild
fn RemoveChild(&self, node: &Node)
-> Fallible<Root<Node>> {
Node::pre_remove(node, self)
}
// https://dom.spec.whatwg.org/#dom-node-normalize
fn Normalize(&self) {
let mut prev_text: Option<Root<Text>> = None;
for child in self.children() {
match TextCast::to_ref(child.r()) {
Some(text) => {
let characterdata: &CharacterData = CharacterDataCast::from_ref(text);
if characterdata.Length() == 0 {
Node::remove(&*child, self, SuppressObserver::Unsuppressed);
} else {
match prev_text {
Some(ref text_node) => {
let prev_characterdata =
CharacterDataCast::from_ref(text_node.r());
prev_characterdata.append_data(&**characterdata.data());
Node::remove(&*child, self, SuppressObserver::Unsuppressed);
},
None => prev_text = Some(Root::from_ref(text))
}
}
},
None => {
child.r().Normalize();
prev_text = None;
}
}
}
}
// https://dom.spec.whatwg.org/#dom-node-clonenode
fn CloneNode(&self, deep: bool) -> Root<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<&Node>) -> bool {
fn is_equal_doctype(node: &Node, other: &Node) -> bool {
let doctype: &DocumentType = DocumentTypeCast::to_ref(node).unwrap();
let other_doctype: &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: &Node, other: &Node) -> bool {
let element: &Element = ElementCast::to_ref(node).unwrap();
let other_element: &Element = ElementCast::to_ref(other).unwrap();
(*element.namespace() == *other_element.namespace()) &&
(*element.prefix() == *other_element.prefix()) &&
(*element.local_name() == *other_element.local_name()) &&
(element.attrs().len() == other_element.attrs().len())
}
fn is_equal_processinginstruction(node: &Node, other: &Node) -> bool {
let pi: &ProcessingInstruction = ProcessingInstructionCast::to_ref(node).unwrap();
let other_pi: &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: &Node, other: &Node) -> bool {
let characterdata: &CharacterData = CharacterDataCast::to_ref(node).unwrap();
let other_characterdata: &CharacterData = CharacterDataCast::to_ref(other).unwrap();
*characterdata.data() == *other_characterdata.data()
}
fn is_equal_element_attrs(node: &Node, other: &Node) -> bool {
let element: &Element = ElementCast::to_ref(node).unwrap();
let other_element: &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().all(|attr| {
let attr = attr.root();
other_element.attrs().iter().any(|other_attr| {
let other_attr = other_attr.root();
(*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: &Node, node: &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.r(), other_child.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: &Node) -> u16 {
if self == other {
// step 2.
0
} else {
let mut lastself = Root::from_ref(self);
let mut lastother = Root::from_ref(other);
for ancestor in self.ancestors() {
if ancestor.r() == other {
// step 4.
return NodeConstants::DOCUMENT_POSITION_CONTAINS +
NodeConstants::DOCUMENT_POSITION_PRECEDING;
}
lastself = ancestor;
}
for ancestor in other.ancestors() {
if ancestor.r() == self {
// step 5.
return NodeConstants::DOCUMENT_POSITION_CONTAINED_BY +
NodeConstants::DOCUMENT_POSITION_FOLLOWING;
}
lastother = ancestor;
}
if lastself != lastother {
let abstract_uint: uintptr_t = as_uintptr(&self);
let other_uint: uintptr_t = as_uintptr(&*other);
let random = if abstract_uint < other_uint {
NodeConstants::DOCUMENT_POSITION_FOLLOWING
} else {
NodeConstants::DOCUMENT_POSITION_PRECEDING
};
// step 3.
return random +
NodeConstants::DOCUMENT_POSITION_DISCONNECTED +
NodeConstants::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
for child in lastself.r().traverse_preorder() {
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<&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> {
let namespace = namespace_from_domstring(namespace);
// Step 1.
if namespace == ns!("") {
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.r().lookup_prefix(namespace)
})
},
NodeTypeId::DocumentType | NodeTypeId::DocumentFragment => None,
_ => {
self.GetParentElement().and_then(|element| {
element.r().lookup_prefix(namespace)
})
}
}
}
// https://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri
fn LookupNamespaceURI(&self, prefix: Option<DOMString>) -> Option<DOMString> {
// Step 1.
let prefix = match prefix {
Some(ref p) if p.is_empty() => None,
pre => pre
};
// Step 2.
Node::namespace_to_string(Node::locate_namespace(self, prefix))
}
// https://dom.spec.whatwg.org/#dom-node-isdefaultnamespace
fn IsDefaultNamespace(&self, namespace: Option<DOMString>) -> bool {
// Step 1.
let namespace = namespace_from_domstring(namespace);
// Steps 2 and 3.
Node::locate_namespace(self, None) == namespace
}
}
/// 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: &T) -> Root<Document> {
let node: &Node = NodeCast::from_ref(derived);
node.owner_doc()
}
pub fn window_from_node<T: NodeBase + Reflectable>(derived: &T) -> Root<Window> {
let document = document_from_node(derived);
document.r().window()
}
impl VirtualMethods for Node {
fn super_type(&self) -> Option<&VirtualMethods> {
let eventtarget: &EventTarget = EventTargetCast::from_ref(self);
Some(eventtarget as &VirtualMethods)
}
fn children_changed(&self, mutation: &ChildrenMutation) {
if let Some(ref s) = self.super_type() {
s.children_changed(mutation);
}
match *mutation {
ChildrenMutation::Append { added, .. } |
ChildrenMutation::Insert { added, .. } |
ChildrenMutation::Prepend { added, .. } => {
self.children_count.set(
self.children_count.get() + added.len() as u32);
},
ChildrenMutation::Replace { added, .. } => {
self.children_count.set(
self.children_count.get() - 1u32 + added.len() as u32);
},
ChildrenMutation::ReplaceAll { added, .. } => {
self.children_count.set(added.len() as u32);
},
}
if let Some(list) = self.child_list.get_rooted() {
list.as_children_list().children_changed(mutation);
}
}
}
impl Node {
pub fn check_ancestors_disabled_state_for_form_control(&self) {
if self.get_disabled_state() { return; }
for ancestor in self.ancestors() {
let ancestor = ancestor;
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()
.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 == *legend) { continue; }
},
None => ()
}
self.set_disabled_state(true);
self.set_enabled_state(false);
return;
}
}
pub fn check_parent_disabled_state_for_option(&self) {
if self.get_disabled_state() { return; }
if let Some(ref parent) = self.GetParentNode() {
if parent.r().is_htmloptgroupelement() && parent.r().get_disabled_state() {
self.set_disabled_state(true);
self.set_enabled_state(false);
}
}
}
pub fn check_disabled_attribute(&self) {
let elem = 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, HeapSizeOf)]
pub enum NodeDamage {
/// The node's `style` attribute changed.
NodeStyleDamaged,
/// Other parts of a node changed; attributes, text content, etc.
OtherNodeDamage,
}
pub enum ChildrenMutation<'a> {
Append { prev: &'a Node, added: &'a [&'a Node] },
Insert { prev: &'a Node, added: &'a [&'a Node], next: &'a Node },
Prepend { added: &'a [&'a Node], next: &'a Node },
Replace {
prev: Option<&'a Node>,
removed: &'a Node,
added: &'a [&'a Node],
next: Option<&'a Node>,
},
ReplaceAll { removed: &'a [&'a Node], added: &'a [&'a Node] },
}
impl<'a> ChildrenMutation<'a> {
fn insert(prev: Option<&'a Node>, added: &'a [&'a Node], next: Option<&'a Node>)
-> ChildrenMutation<'a> {
match (prev, next) {
(None, None) => {
ChildrenMutation::ReplaceAll { removed: &[], added: added }
},
(Some(prev), None) => {
ChildrenMutation::Append { prev: prev, added: added }
},
(None, Some(next)) => {
ChildrenMutation::Prepend { added: added, next: next }
},
(Some(prev), Some(next)) => {
ChildrenMutation::Insert { prev: prev, added: added, next: next }
},
}
}
fn replace(prev: Option<&'a Node>,
removed: &'a &'a Node,
added: &'a [&'a Node],
next: Option<&'a Node>)
-> ChildrenMutation<'a> {
if let (None, None) = (prev, next) {
ChildrenMutation::ReplaceAll {
removed: ref_slice(removed),
added: added,
}
} else {
ChildrenMutation::Replace {
prev: prev,
removed: *removed,
added: added,
next: next,
}
}
}
fn replace_all(removed: &'a [&'a Node], added: &'a [&'a Node])
-> ChildrenMutation<'a> {
ChildrenMutation::ReplaceAll { removed: removed, added: added }
}
}