mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
http://www.robohornet.org gives a score of 101.36 on master, and 102.68 with this PR. The latter is slightly better, but probably within noise level. So it looks like this PR does not affect DOM performance. This is expected since `Box::new` is defined as: ```rust impl<T> Box<T> { #[inline(always)] pub fn new(x: T) -> Box<T> { box x } } ``` With inlining, it should compile to the same as box syntax.
2820 lines
101 KiB
Rust
2820 lines
101 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 devtools_traits::NodeInfo;
|
|
use document_loader::DocumentLoader;
|
|
use dom::bindings::cell::DomRefCell;
|
|
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::HTMLCollectionBinding::HTMLCollectionMethods;
|
|
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::Bindings::WindowBinding::WindowMethods;
|
|
use dom::bindings::codegen::UnionTypes::NodeOrString;
|
|
use dom::bindings::conversions::{self, DerivedFrom};
|
|
use dom::bindings::error::{Error, ErrorResult, Fallible};
|
|
use dom::bindings::inheritance::{Castable, CharacterDataTypeId, ElementTypeId};
|
|
use dom::bindings::inheritance::{EventTargetTypeId, HTMLElementTypeId, NodeTypeId};
|
|
use dom::bindings::inheritance::{SVGElementTypeId, SVGGraphicsElementTypeId};
|
|
use dom::bindings::reflector::{DomObject, reflect_dom_object};
|
|
use dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom, RootedReference};
|
|
use dom::bindings::str::{DOMString, USVString};
|
|
use dom::bindings::xmlname::namespace_from_domstring;
|
|
use dom::characterdata::{CharacterData, LayoutCharacterDataHelpers};
|
|
use dom::cssstylesheet::CSSStyleSheet;
|
|
use dom::customelementregistry::{CallbackReaction, try_upgrade_element};
|
|
use dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument};
|
|
use dom::documentfragment::DocumentFragment;
|
|
use dom::documenttype::DocumentType;
|
|
use dom::element::{CustomElementCreationMode, Element, ElementCreator};
|
|
use dom::eventtarget::EventTarget;
|
|
use dom::globalscope::GlobalScope;
|
|
use dom::htmlbodyelement::HTMLBodyElement;
|
|
use dom::htmlcanvaselement::{HTMLCanvasElement, LayoutHTMLCanvasElementHelpers};
|
|
use dom::htmlcollection::HTMLCollection;
|
|
use dom::htmlelement::HTMLElement;
|
|
use dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods};
|
|
use dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
|
|
use dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers};
|
|
use dom::htmllinkelement::HTMLLinkElement;
|
|
use dom::htmlmetaelement::HTMLMetaElement;
|
|
use dom::htmlstyleelement::HTMLStyleElement;
|
|
use dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers};
|
|
use dom::mutationobserver::{Mutation, MutationObserver, RegisteredObserver};
|
|
use dom::nodelist::NodeList;
|
|
use dom::processinginstruction::ProcessingInstruction;
|
|
use dom::range::WeakRangeVec;
|
|
use dom::svgsvgelement::{SVGSVGElement, LayoutSVGSVGElementHelpers};
|
|
use dom::text::Text;
|
|
use dom::virtualmethods::{VirtualMethods, vtable_for};
|
|
use dom::window::Window;
|
|
use dom_struct::dom_struct;
|
|
use euclid::{Point2D, Vector2D, Rect, Size2D};
|
|
use heapsize::{HeapSizeOf, heap_size_of};
|
|
use html5ever::{Prefix, Namespace, QualName};
|
|
use js::jsapi::{JSContext, JSObject, JSRuntime};
|
|
use libc::{self, c_void, uintptr_t};
|
|
use msg::constellation_msg::{BrowsingContextId, PipelineId};
|
|
use ref_slice::ref_slice;
|
|
use script_layout_interface::{HTMLCanvasData, OpaqueStyleAndLayoutData, SVGSVGData};
|
|
use script_layout_interface::{LayoutElementType, LayoutNodeType, TrustedNodeAddress};
|
|
use script_layout_interface::message::Msg;
|
|
use script_thread::ScriptThread;
|
|
use script_traits::DocumentActivity;
|
|
use script_traits::UntrustedNodeAddress;
|
|
use selectors::matching::{matches_selector_list, MatchingContext, MatchingMode};
|
|
use selectors::parser::SelectorList;
|
|
use servo_arc::Arc;
|
|
use servo_url::ServoUrl;
|
|
use smallvec::SmallVec;
|
|
use std::borrow::ToOwned;
|
|
use std::cell::{Cell, UnsafeCell, RefMut};
|
|
use std::cmp;
|
|
use std::default::Default;
|
|
use std::iter;
|
|
use std::mem;
|
|
use std::ops::Range;
|
|
use style::context::QuirksMode;
|
|
use style::dom::OpaqueNode;
|
|
use style::selector_parser::{SelectorImpl, SelectorParser};
|
|
use style::stylesheets::Stylesheet;
|
|
use style::thread_state;
|
|
use uuid::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: MutNullableDom<Node>,
|
|
|
|
/// The first child of this node.
|
|
first_child: MutNullableDom<Node>,
|
|
|
|
/// The last child of this node.
|
|
last_child: MutNullableDom<Node>,
|
|
|
|
/// The next sibling of this node.
|
|
next_sibling: MutNullableDom<Node>,
|
|
|
|
/// The previous sibling of this node.
|
|
prev_sibling: MutNullableDom<Node>,
|
|
|
|
/// The document that this node belongs to.
|
|
owner_doc: MutNullableDom<Document>,
|
|
|
|
/// The live list of children return by .childNodes.
|
|
child_list: MutNullableDom<NodeList>,
|
|
|
|
/// The live count of children of this node.
|
|
children_count: Cell<u32>,
|
|
|
|
/// A bitfield of flags for node items.
|
|
flags: Cell<NodeFlags>,
|
|
|
|
/// The maximum version of any inclusive descendant of this node.
|
|
inclusive_descendants_version: Cell<u64>,
|
|
|
|
/// A vector of weak references to Range instances of which the start
|
|
/// or end containers are this node. No range should ever be found
|
|
/// twice in this vector, even if both the start and end containers
|
|
/// are this node.
|
|
ranges: WeakRangeVec,
|
|
|
|
/// Style+Layout information. Only the layout thread may touch this data.
|
|
///
|
|
/// Must be sent back to the layout thread to be destroyed when this
|
|
/// node is finalized.
|
|
style_and_layout_data: Cell<Option<OpaqueStyleAndLayoutData>>,
|
|
|
|
/// Registered observers for this node.
|
|
mutation_observers: DomRefCell<Vec<RegisteredObserver>>,
|
|
|
|
unique_id: UniqueId,
|
|
}
|
|
|
|
bitflags! {
|
|
#[doc = "Flags for node items."]
|
|
#[derive(HeapSizeOf, JSTraceable)]
|
|
pub flags NodeFlags: u16 {
|
|
#[doc = "Specifies whether this node is in a document."]
|
|
const IS_IN_DOC = 1 << 0,
|
|
|
|
#[doc = "Specifies whether this node needs style recalc on next reflow."]
|
|
const HAS_DIRTY_DESCENDANTS = 1 << 1,
|
|
// 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 = 1 << 2,
|
|
#[doc = "Specifies whether this node is focusable and whether it is supposed \
|
|
to be reachable with using sequential focus navigation."]
|
|
const SEQUENTIALLY_FOCUSABLE = 1 << 3,
|
|
|
|
/// Whether any ancestor is a fragmentation container
|
|
const CAN_BE_FRAGMENTED = 1 << 4,
|
|
|
|
// There's a free bit here.
|
|
|
|
#[doc = "Specifies whether the parser has set an associated form owner for \
|
|
this element. Only applicable for form-associatable elements."]
|
|
const PARSER_ASSOCIATED_FORM_OWNER = 1 << 6,
|
|
|
|
/// Whether this element has a snapshot stored due to a style or
|
|
/// attribute change.
|
|
///
|
|
/// See the `style::restyle_hints` module.
|
|
const HAS_SNAPSHOT = 1 << 7,
|
|
|
|
/// Whether this element has already handled the stored snapshot.
|
|
const HANDLED_SNAPSHOT = 1 << 8,
|
|
}
|
|
}
|
|
|
|
impl NodeFlags {
|
|
pub fn new() -> NodeFlags {
|
|
NodeFlags::empty()
|
|
}
|
|
}
|
|
|
|
impl Drop for Node {
|
|
#[allow(unsafe_code)]
|
|
fn drop(&mut self) {
|
|
self.style_and_layout_data.get().map(|d| self.dispose(d));
|
|
}
|
|
}
|
|
|
|
/// suppress observers flag
|
|
/// https://dom.spec.whatwg.org/#concept-node-insert
|
|
/// https://dom.spec.whatwg.org/#concept-node-remove
|
|
#[derive(Clone, Copy, HeapSizeOf)]
|
|
enum SuppressObserver {
|
|
Suppressed,
|
|
Unsuppressed
|
|
}
|
|
|
|
impl Node {
|
|
/// Sends the style and layout data, if any, back to the layout thread to be destroyed.
|
|
pub fn dispose(&self, data: OpaqueStyleAndLayoutData) {
|
|
debug_assert!(thread_state::get().is_script());
|
|
let win = window_from_node(self);
|
|
self.style_and_layout_data.set(None);
|
|
if win.layout_chan().send(Msg::ReapStyleAndLayoutData(data)).is_err() {
|
|
warn!("layout thread unreachable - leaking layout data");
|
|
}
|
|
}
|
|
|
|
/// 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().r() == Some(self));
|
|
let prev_sibling = before.GetPreviousSibling();
|
|
match prev_sibling {
|
|
None => {
|
|
assert!(Some(*before) == self.first_child.get().r());
|
|
self.first_child.set(Some(new_child));
|
|
},
|
|
Some(ref prev_sibling) => {
|
|
prev_sibling.next_sibling.set(Some(new_child));
|
|
new_child.prev_sibling.set(Some(&prev_sibling));
|
|
},
|
|
}
|
|
before.prev_sibling.set(Some(new_child));
|
|
new_child.next_sibling.set(Some(before));
|
|
},
|
|
None => {
|
|
let last_child = self.GetLastChild();
|
|
match last_child {
|
|
None => self.first_child.set(Some(new_child)),
|
|
Some(ref last_child) => {
|
|
assert!(last_child.next_sibling.get().is_none());
|
|
last_child.next_sibling.set(Some(new_child));
|
|
new_child.prev_sibling.set(Some(&last_child));
|
|
}
|
|
}
|
|
|
|
self.last_child.set(Some(new_child));
|
|
},
|
|
}
|
|
|
|
new_child.parent_node.set(Some(self));
|
|
self.children_count.set(self.children_count.get() + 1);
|
|
|
|
let parent_in_doc = self.is_in_doc();
|
|
for node in new_child.traverse_preorder() {
|
|
node.set_flag(IS_IN_DOC, parent_in_doc);
|
|
// Out-of-document elements never have the descendants flag set.
|
|
debug_assert!(!node.get_flag(HAS_DIRTY_DESCENDANTS));
|
|
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, cached_index: Option<u32>) {
|
|
assert!(child.parent_node.get().r() == Some(self));
|
|
let prev_sibling = child.GetPreviousSibling();
|
|
match prev_sibling {
|
|
None => {
|
|
self.first_child.set(child.next_sibling.get().r());
|
|
}
|
|
Some(ref prev_sibling) => {
|
|
prev_sibling.next_sibling.set(child.next_sibling.get().r());
|
|
}
|
|
}
|
|
let next_sibling = child.GetNextSibling();
|
|
match next_sibling {
|
|
None => {
|
|
self.last_child.set(child.prev_sibling.get().r());
|
|
}
|
|
Some(ref next_sibling) => {
|
|
next_sibling.prev_sibling.set(child.prev_sibling.get().r());
|
|
}
|
|
}
|
|
|
|
let context = UnbindContext::new(self, prev_sibling.r(), cached_index);
|
|
|
|
child.prev_sibling.set(None);
|
|
child.next_sibling.set(None);
|
|
child.parent_node.set(None);
|
|
self.children_count.set(self.children_count.get() - 1);
|
|
|
|
for node in child.traverse_preorder() {
|
|
// Out-of-document elements never have the descendants flag set.
|
|
node.set_flag(IS_IN_DOC | HAS_DIRTY_DESCENDANTS |
|
|
HAS_SNAPSHOT | HANDLED_SNAPSHOT,
|
|
false);
|
|
}
|
|
for node in child.traverse_preorder() {
|
|
// This needs to be in its own loop, because unbind_from_tree may
|
|
// rely on the state of IS_IN_DOC of the context node's descendants,
|
|
// e.g. when removing a <form>.
|
|
vtable_for(&&*node).unbind_from_tree(&context);
|
|
node.style_and_layout_data.get().map(|d| node.dispose(d));
|
|
// https://dom.spec.whatwg.org/#concept-node-remove step 14
|
|
if let Some(element) = node.as_custom_element() {
|
|
ScriptThread::enqueue_callback_reaction(&*element, CallbackReaction::Disconnected, None);
|
|
}
|
|
}
|
|
|
|
self.owner_doc().content_and_heritage_changed(self, NodeDamage::OtherNodeDamage);
|
|
child.owner_doc().content_and_heritage_changed(child, NodeDamage::OtherNodeDamage);
|
|
}
|
|
|
|
pub fn to_untrusted_node_address(&self) -> UntrustedNodeAddress {
|
|
UntrustedNodeAddress(self.reflector().get_jsobject().get() as *const c_void)
|
|
}
|
|
|
|
pub fn as_custom_element(&self) -> Option<DomRoot<Element>> {
|
|
self.downcast::<Element>()
|
|
.and_then(|element| if element.get_custom_element_definition().is_some() {
|
|
Some(DomRoot::from_ref(element))
|
|
} else {
|
|
None
|
|
})
|
|
}
|
|
}
|
|
|
|
pub struct QuerySelectorIterator {
|
|
selectors: SelectorList<SelectorImpl>,
|
|
iterator: TreeIterator,
|
|
}
|
|
|
|
impl<'a> QuerySelectorIterator {
|
|
fn new(iter: TreeIterator, selectors: SelectorList<SelectorImpl>)
|
|
-> QuerySelectorIterator {
|
|
QuerySelectorIterator {
|
|
selectors: selectors,
|
|
iterator: iter,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for QuerySelectorIterator {
|
|
type Item = DomRoot<Node>;
|
|
|
|
fn next(&mut self) -> Option<DomRoot<Node>> {
|
|
let selectors = &self.selectors;
|
|
|
|
self.iterator.by_ref().filter_map(|node| {
|
|
// TODO(cgaebel): Is it worth it to build a bloom filter here
|
|
// (instead of passing `None`)? Probably.
|
|
//
|
|
// FIXME(bholley): Consider an nth-index cache here.
|
|
let mut ctx = MatchingContext::new(MatchingMode::Normal, None, None,
|
|
node.owner_doc().quirks_mode());
|
|
if let Some(element) = DomRoot::downcast(node) {
|
|
if matches_selector_list(selectors, &element, &mut ctx) {
|
|
return Some(DomRoot::upcast(element));
|
|
}
|
|
}
|
|
None
|
|
}).next()
|
|
}
|
|
}
|
|
|
|
|
|
impl Node {
|
|
pub fn teardown(&self) {
|
|
self.style_and_layout_data.get().map(|d| self.dispose(d));
|
|
for kid in self.children() {
|
|
kid.teardown();
|
|
}
|
|
}
|
|
|
|
/// Returns true if this node is before `other` in the same connected DOM
|
|
/// tree.
|
|
pub fn is_before(&self, other: &Node) -> bool {
|
|
let cmp = other.CompareDocumentPosition(self);
|
|
if cmp & NodeConstants::DOCUMENT_POSITION_DISCONNECTED != 0 {
|
|
return false;
|
|
}
|
|
|
|
cmp & NodeConstants::DOCUMENT_POSITION_PRECEDING != 0
|
|
}
|
|
|
|
/// Return all registered mutation observers for this node.
|
|
pub fn registered_mutation_observers(&self) -> RefMut<Vec<RegisteredObserver>> {
|
|
self.mutation_observers.borrow_mut()
|
|
}
|
|
|
|
/// 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.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(_) => {
|
|
self.downcast::<CharacterData>().unwrap().Length()
|
|
},
|
|
_ => self.children_count(),
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#concept-tree-index
|
|
pub fn index(&self) -> u32 {
|
|
self.preceding_siblings().count() as u32
|
|
}
|
|
|
|
/// Returns true if this node has a parent.
|
|
pub fn has_parent(&self) -> bool {
|
|
self.parent_node.get().is_some()
|
|
}
|
|
|
|
pub fn children_count(&self) -> u32 {
|
|
self.children_count.get()
|
|
}
|
|
|
|
pub fn ranges(&self) -> &WeakRangeVec {
|
|
&self.ranges
|
|
}
|
|
|
|
#[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 has_dirty_descendants(&self) -> bool {
|
|
self.get_flag(HAS_DIRTY_DESCENDANTS)
|
|
}
|
|
|
|
pub fn rev_version(&self) {
|
|
// The new version counter is 1 plus the max of the node's current version counter,
|
|
// its descendants version, and the document's version. Normally, this will just be
|
|
// the document's version, but we do have to deal with the case where the node has moved
|
|
// document, so may have a higher version count than its owning document.
|
|
let doc: DomRoot<Node> = DomRoot::upcast(self.owner_doc());
|
|
let version = cmp::max(self.inclusive_descendants_version(), doc.inclusive_descendants_version()) + 1;
|
|
for ancestor in self.inclusive_ancestors() {
|
|
ancestor.inclusive_descendants_version.set(version);
|
|
}
|
|
doc.inclusive_descendants_version.set(version);
|
|
}
|
|
|
|
pub fn dirty(&self, damage: NodeDamage) {
|
|
self.rev_version();
|
|
if !self.is_in_doc() {
|
|
return;
|
|
}
|
|
|
|
match self.type_id() {
|
|
NodeTypeId::CharacterData(CharacterDataTypeId::Text) =>
|
|
self.parent_node.get().unwrap().downcast::<Element>().unwrap().restyle(damage),
|
|
NodeTypeId::Element(_) =>
|
|
self.downcast::<Element>().unwrap().restyle(damage),
|
|
_ => {},
|
|
};
|
|
}
|
|
|
|
/// The maximum version number of this node's descendants, including itself
|
|
pub fn inclusive_descendants_version(&self) -> u64 {
|
|
self.inclusive_descendants_version.get()
|
|
}
|
|
|
|
/// 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) -> impl Iterator<Item=DomRoot<Node>> {
|
|
SimpleNodeIterator {
|
|
current: Some(DomRoot::from_ref(self)),
|
|
next_node: |n| n.GetNextSibling(),
|
|
}
|
|
}
|
|
|
|
pub fn inclusively_preceding_siblings(&self) -> impl Iterator<Item=DomRoot<Node>> {
|
|
SimpleNodeIterator {
|
|
current: Some(DomRoot::from_ref(self)),
|
|
next_node: |n| n.GetPreviousSibling(),
|
|
}
|
|
}
|
|
|
|
pub fn is_inclusive_ancestor_of(&self, parent: &Node) -> bool {
|
|
self == parent || self.is_ancestor_of(parent)
|
|
}
|
|
|
|
pub fn is_ancestor_of(&self, parent: &Node) -> bool {
|
|
parent.ancestors().any(|ancestor| &*ancestor == self)
|
|
}
|
|
|
|
pub fn following_siblings(&self) -> impl Iterator<Item=DomRoot<Node>> {
|
|
SimpleNodeIterator {
|
|
current: self.GetNextSibling(),
|
|
next_node: |n| n.GetNextSibling(),
|
|
}
|
|
}
|
|
|
|
pub fn preceding_siblings(&self) -> impl Iterator<Item=DomRoot<Node>> {
|
|
SimpleNodeIterator {
|
|
current: self.GetPreviousSibling(),
|
|
next_node: |n| n.GetPreviousSibling(),
|
|
}
|
|
}
|
|
|
|
pub fn following_nodes(&self, root: &Node) -> FollowingNodeIterator {
|
|
FollowingNodeIterator {
|
|
current: Some(DomRoot::from_ref(self)),
|
|
root: DomRoot::from_ref(root),
|
|
}
|
|
}
|
|
|
|
pub fn preceding_nodes(&self, root: &Node) -> PrecedingNodeIterator {
|
|
PrecedingNodeIterator {
|
|
current: Some(DomRoot::from_ref(self)),
|
|
root: DomRoot::from_ref(root),
|
|
}
|
|
}
|
|
|
|
pub fn descending_last_children(&self) -> impl Iterator<Item=DomRoot<Node>> {
|
|
SimpleNodeIterator {
|
|
current: self.GetLastChild(),
|
|
next_node: |n| n.GetLastChild(),
|
|
}
|
|
}
|
|
|
|
pub fn is_parent_of(&self, child: &Node) -> bool {
|
|
child.parent_node.get().map_or(false, |parent| &*parent == self)
|
|
}
|
|
|
|
pub fn to_trusted_node_address(&self) -> TrustedNodeAddress {
|
|
TrustedNodeAddress(&*self as *const Node as *const libc::c_void)
|
|
}
|
|
|
|
/// Returns the rendered bounding content box if the element is rendered,
|
|
/// and none otherwise.
|
|
pub fn bounding_content_box(&self) -> Option<Rect<Au>> {
|
|
window_from_node(self)
|
|
.content_box_query(self.to_trusted_node_address())
|
|
}
|
|
|
|
pub fn bounding_content_box_or_zero(&self) -> Rect<Au> {
|
|
self.bounding_content_box().unwrap_or_else(Rect::zero)
|
|
}
|
|
|
|
pub fn content_boxes(&self) -> Vec<Rect<Au>> {
|
|
window_from_node(self).content_boxes_query(self.to_trusted_node_address())
|
|
}
|
|
|
|
pub fn client_rect(&self) -> Rect<i32> {
|
|
window_from_node(self).client_rect_query(self.to_trusted_node_address())
|
|
}
|
|
|
|
// https://drafts.csswg.org/cssom-view/#dom-element-scrollwidth
|
|
// https://drafts.csswg.org/cssom-view/#dom-element-scrollheight
|
|
// https://drafts.csswg.org/cssom-view/#dom-element-scrolltop
|
|
// https://drafts.csswg.org/cssom-view/#dom-element-scrollleft
|
|
pub fn scroll_area(&self) -> Rect<i32> {
|
|
// Step 1
|
|
let document = self.owner_doc();
|
|
// Step 3
|
|
let window = document.window();
|
|
|
|
let html_element = document.GetDocumentElement();
|
|
|
|
let is_body_element = self.downcast::<HTMLBodyElement>()
|
|
.map_or(false, |e| e.is_the_html_body_element());
|
|
|
|
let scroll_area = window.scroll_area_query(self.to_trusted_node_address());
|
|
|
|
match (document != window.Document(), is_body_element, document.quirks_mode(),
|
|
html_element.r() == self.downcast::<Element>()) {
|
|
// Step 2 && Step 5
|
|
(true, _, _, _) | (_, false, QuirksMode::Quirks, true) => Rect::zero(),
|
|
// Step 6 && Step 7
|
|
(false, false, _, true) | (false, true, QuirksMode::Quirks, _) => {
|
|
Rect::new(Point2D::new(window.ScrollX(), window.ScrollY()),
|
|
Size2D::new(cmp::max(window.InnerWidth(), scroll_area.size.width),
|
|
cmp::max(window.InnerHeight(), scroll_area.size.height)))
|
|
},
|
|
// Step 9
|
|
_ => scroll_area
|
|
}
|
|
}
|
|
|
|
pub fn scroll_offset(&self) -> Vector2D<f32> {
|
|
let document = self.owner_doc();
|
|
let window = document.window();
|
|
window.scroll_offset_query(self)
|
|
}
|
|
|
|
// 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(parent) => parent,
|
|
};
|
|
|
|
// Step 3.
|
|
let viable_previous_sibling = first_node_not_in(self.preceding_siblings(), &nodes);
|
|
|
|
// Step 4.
|
|
let node = 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(),
|
|
};
|
|
|
|
// Step 6.
|
|
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(parent) => parent,
|
|
};
|
|
|
|
// Step 3.
|
|
let viable_next_sibling = first_node_not_in(self.following_siblings(), &nodes);
|
|
|
|
// Step 4.
|
|
let node = self.owner_doc().node_from_nodes_and_strings(nodes)?;
|
|
|
|
// Step 5.
|
|
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 {
|
|
// Step 1.
|
|
let parent = if let Some(parent) = self.GetParentNode() {
|
|
parent
|
|
} else {
|
|
// Step 2.
|
|
return Ok(());
|
|
};
|
|
// Step 3.
|
|
let viable_next_sibling = first_node_not_in(self.following_siblings(), &nodes);
|
|
// Step 4.
|
|
let node = self.owner_doc().node_from_nodes_and_strings(nodes)?;
|
|
if self.parent_node == Some(&*parent) {
|
|
// Step 5.
|
|
parent.ReplaceChild(&node, self)?;
|
|
} else {
|
|
// Step 6.
|
|
Node::pre_insert(&node, &parent, viable_next_sibling.r())?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// 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 = doc.node_from_nodes_and_strings(nodes)?;
|
|
// Step 2.
|
|
let first_child = self.first_child.get();
|
|
Node::pre_insert(&node, 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 = doc.node_from_nodes_and_strings(nodes)?;
|
|
// Step 2.
|
|
self.AppendChild(&node).map(|_| ())
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-parentnode-queryselector
|
|
pub fn query_selector(&self, selectors: DOMString) -> Fallible<Option<DomRoot<Element>>> {
|
|
// Step 1.
|
|
match SelectorParser::parse_author_origin_no_namespace(&selectors) {
|
|
// Step 2.
|
|
Err(_) => Err(Error::Syntax),
|
|
// Step 3.
|
|
Ok(selectors) => {
|
|
// FIXME(bholley): Consider an nth-index cache here.
|
|
let mut ctx = MatchingContext::new(MatchingMode::Normal, None, None,
|
|
self.owner_doc().quirks_mode());
|
|
Ok(self.traverse_preorder().filter_map(DomRoot::downcast).find(|element| {
|
|
matches_selector_list(&selectors, element, &mut ctx)
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
pub fn query_selector_iter(&self, selectors: DOMString)
|
|
-> Fallible<QuerySelectorIterator> {
|
|
// Step 1.
|
|
match SelectorParser::parse_author_origin_no_namespace(&selectors) {
|
|
// Step 2.
|
|
Err(_) => Err(Error::Syntax),
|
|
// Step 3.
|
|
Ok(selectors) => {
|
|
let mut descendants = self.traverse_preorder();
|
|
// Skip the root of the tree.
|
|
assert!(&*descendants.next().unwrap() == self);
|
|
Ok(QuerySelectorIterator::new(descendants, selectors))
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
|
|
#[allow(unsafe_code)]
|
|
pub fn query_selector_all(&self, selectors: DOMString) -> Fallible<DomRoot<NodeList>> {
|
|
let window = window_from_node(self);
|
|
let iter = self.query_selector_iter(selectors)?;
|
|
Ok(NodeList::new_simple_list(&window, iter))
|
|
}
|
|
|
|
pub fn ancestors(&self) -> impl Iterator<Item=DomRoot<Node>> {
|
|
SimpleNodeIterator {
|
|
current: self.GetParentNode(),
|
|
next_node: |n| n.GetParentNode(),
|
|
}
|
|
}
|
|
|
|
pub fn inclusive_ancestors(&self) -> impl Iterator<Item=DomRoot<Node>> {
|
|
SimpleNodeIterator {
|
|
current: Some(DomRoot::from_ref(self)),
|
|
next_node: |n| n.GetParentNode(),
|
|
}
|
|
}
|
|
|
|
pub fn owner_doc(&self) -> DomRoot<Document> {
|
|
self.owner_doc.get().unwrap()
|
|
}
|
|
|
|
pub fn set_owner_doc(&self, document: &Document) {
|
|
self.owner_doc.set(Some(document));
|
|
}
|
|
|
|
pub fn is_in_html_doc(&self) -> bool {
|
|
self.owner_doc().is_html_document()
|
|
}
|
|
|
|
pub fn is_in_doc_with_browsing_context(&self) -> bool {
|
|
self.is_in_doc() && self.owner_doc().browsing_context().is_some()
|
|
}
|
|
|
|
pub fn children(&self) -> impl Iterator<Item=DomRoot<Node>> {
|
|
SimpleNodeIterator {
|
|
current: self.GetFirstChild(),
|
|
next_node: |n| n.GetNextSibling(),
|
|
}
|
|
}
|
|
|
|
pub fn rev_children(&self) -> impl Iterator<Item=DomRoot<Node>> {
|
|
SimpleNodeIterator {
|
|
current: self.GetLastChild(),
|
|
next_node: |n| n.GetPreviousSibling(),
|
|
}
|
|
}
|
|
|
|
pub fn child_elements(&self) -> impl Iterator<Item=DomRoot<Element>> {
|
|
self.children().filter_map(DomRoot::downcast as fn(_) -> _).peekable()
|
|
}
|
|
|
|
pub fn remove_self(&self) {
|
|
if let Some(ref parent) = self.GetParentNode() {
|
|
Node::remove(self, &parent, SuppressObserver::Unsuppressed);
|
|
}
|
|
}
|
|
|
|
pub fn unique_id(&self) -> String {
|
|
self.unique_id.borrow().simple().to_string()
|
|
}
|
|
|
|
pub fn summarize(&self) -> NodeInfo {
|
|
let USVString(base_uri) = self.BaseURI();
|
|
NodeInfo {
|
|
uniqueId: self.unique_id(),
|
|
baseURI: base_uri,
|
|
parent: self.GetParentNode().map_or("".to_owned(), |node| node.unique_id()),
|
|
nodeType: self.NodeType(),
|
|
namespaceURI: String::new(), //FIXME
|
|
nodeName: String::from(self.NodeName()),
|
|
numChildren: self.ChildNodes().Length() as usize,
|
|
|
|
//FIXME doctype nodes only
|
|
name: String::new(),
|
|
publicId: String::new(),
|
|
systemId: String::new(),
|
|
attrs: self.downcast().map(Element::summarize).unwrap_or(vec![]),
|
|
|
|
isDocumentElement:
|
|
self.owner_doc()
|
|
.GetDocumentElement()
|
|
.map_or(false, |elem| elem.upcast::<Node>() == self),
|
|
|
|
shortValue: self.GetNodeValue().map(String::from).unwrap_or_default(), //FIXME: truncate
|
|
incompleteValue: false, //FIXME: reflect truncation
|
|
}
|
|
}
|
|
|
|
/// Used by `HTMLTableSectionElement::InsertRow` and `HTMLTableRowElement::InsertCell`
|
|
pub fn insert_cell_or_row<F, G, I>(&self, index: i32, get_items: F, new_child: G) -> Fallible<DomRoot<HTMLElement>>
|
|
where F: Fn() -> DomRoot<HTMLCollection>,
|
|
G: Fn() -> DomRoot<I>,
|
|
I: DerivedFrom<Node> + DerivedFrom<HTMLElement> + DomObject,
|
|
{
|
|
if index < -1 {
|
|
return Err(Error::IndexSize);
|
|
}
|
|
|
|
let tr = new_child();
|
|
|
|
|
|
{
|
|
let tr_node = tr.upcast::<Node>();
|
|
if index == -1 {
|
|
self.InsertBefore(tr_node, None)?;
|
|
} else {
|
|
let items = get_items();
|
|
let node = match items.elements_iter()
|
|
.map(DomRoot::upcast::<Node>)
|
|
.map(Some)
|
|
.chain(iter::once(None))
|
|
.nth(index as usize) {
|
|
None => return Err(Error::IndexSize),
|
|
Some(node) => node,
|
|
};
|
|
self.InsertBefore(tr_node, node.r())?;
|
|
}
|
|
}
|
|
|
|
Ok(DomRoot::upcast::<HTMLElement>(tr))
|
|
}
|
|
|
|
/// Used by `HTMLTableSectionElement::DeleteRow` and `HTMLTableRowElement::DeleteCell`
|
|
pub fn delete_cell_or_row<F, G>(&self, index: i32, get_items: F, is_delete_type: G) -> ErrorResult
|
|
where F: Fn() -> DomRoot<HTMLCollection>,
|
|
G: Fn(&Element) -> bool
|
|
{
|
|
let element = match index {
|
|
index if index < -1 => return Err(Error::IndexSize),
|
|
-1 => {
|
|
let last_child = self.upcast::<Node>().GetLastChild();
|
|
match last_child.and_then(|node| node.inclusively_preceding_siblings()
|
|
.filter_map(DomRoot::downcast::<Element>)
|
|
.filter(|elem| is_delete_type(elem))
|
|
.next()) {
|
|
Some(element) => element,
|
|
None => return Ok(()),
|
|
}
|
|
},
|
|
index => match get_items().Item(index as u32) {
|
|
Some(element) => element,
|
|
None => return Err(Error::IndexSize),
|
|
},
|
|
};
|
|
|
|
element.upcast::<Node>().remove_self();
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
|
|
if let Some(node) = self.downcast::<HTMLStyleElement>() {
|
|
node.get_stylesheet()
|
|
} else if let Some(node) = self.downcast::<HTMLLinkElement>() {
|
|
node.get_stylesheet()
|
|
} else if let Some(node) = self.downcast::<HTMLMetaElement>() {
|
|
node.get_stylesheet()
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn get_cssom_stylesheet(&self) -> Option<DomRoot<CSSStyleSheet>> {
|
|
if let Some(node) = self.downcast::<HTMLStyleElement>() {
|
|
node.get_cssom_stylesheet()
|
|
} else if let Some(node) = self.downcast::<HTMLLinkElement>() {
|
|
node.get_cssom_stylesheet()
|
|
} else if let Some(node) = self.downcast::<HTMLMetaElement>() {
|
|
node.get_cssom_stylesheet()
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// 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<DomRoot<Node>>
|
|
where I: Iterator<Item=DomRoot<Node>>
|
|
{
|
|
nodes.find(|node| {
|
|
not_in.iter().all(|n| {
|
|
match *n {
|
|
NodeOrString::Node(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 unsafe fn from_untrusted_node_address(_runtime: *mut JSRuntime, candidate: UntrustedNodeAddress)
|
|
-> DomRoot<Node> {
|
|
// 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 `Dom<Node>` from an invalid pointer!")
|
|
}
|
|
let boxed_node = conversions::private_from_object(object) as *const Node;
|
|
DomRoot::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<LayoutDom<Node>>;
|
|
unsafe fn first_child_ref(&self) -> Option<LayoutDom<Node>>;
|
|
unsafe fn last_child_ref(&self) -> Option<LayoutDom<Node>>;
|
|
unsafe fn prev_sibling_ref(&self) -> Option<LayoutDom<Node>>;
|
|
unsafe fn next_sibling_ref(&self) -> Option<LayoutDom<Node>>;
|
|
|
|
unsafe fn owner_doc_for_layout(&self) -> LayoutDom<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 get_style_and_layout_data(&self) -> Option<OpaqueStyleAndLayoutData>;
|
|
unsafe fn init_style_and_layout_data(&self, OpaqueStyleAndLayoutData);
|
|
unsafe fn take_style_and_layout_data(&self) -> OpaqueStyleAndLayoutData;
|
|
|
|
fn text_content(&self) -> String;
|
|
fn selection(&self) -> Option<Range<usize>>;
|
|
fn image_url(&self) -> Option<ServoUrl>;
|
|
fn canvas_data(&self) -> Option<HTMLCanvasData>;
|
|
fn svg_data(&self) -> Option<SVGSVGData>;
|
|
fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId>;
|
|
fn iframe_pipeline_id(&self) -> Option<PipelineId>;
|
|
fn opaque(&self) -> OpaqueNode;
|
|
}
|
|
|
|
impl LayoutNodeHelpers for LayoutDom<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<LayoutDom<Node>> {
|
|
(*self.unsafe_get()).parent_node.get_inner_as_layout()
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(unsafe_code)]
|
|
unsafe fn first_child_ref(&self) -> Option<LayoutDom<Node>> {
|
|
(*self.unsafe_get()).first_child.get_inner_as_layout()
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(unsafe_code)]
|
|
unsafe fn last_child_ref(&self) -> Option<LayoutDom<Node>> {
|
|
(*self.unsafe_get()).last_child.get_inner_as_layout()
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(unsafe_code)]
|
|
unsafe fn prev_sibling_ref(&self) -> Option<LayoutDom<Node>> {
|
|
(*self.unsafe_get()).prev_sibling.get_inner_as_layout()
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(unsafe_code)]
|
|
unsafe fn next_sibling_ref(&self) -> Option<LayoutDom<Node>> {
|
|
(*self.unsafe_get()).next_sibling.get_inner_as_layout()
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(unsafe_code)]
|
|
unsafe fn owner_doc_for_layout(&self) -> LayoutDom<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 get_style_and_layout_data(&self) -> Option<OpaqueStyleAndLayoutData> {
|
|
(*self.unsafe_get()).style_and_layout_data.get()
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(unsafe_code)]
|
|
unsafe fn init_style_and_layout_data(&self, val: OpaqueStyleAndLayoutData) {
|
|
debug_assert!((*self.unsafe_get()).style_and_layout_data.get().is_none());
|
|
(*self.unsafe_get()).style_and_layout_data.set(Some(val));
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(unsafe_code)]
|
|
unsafe fn take_style_and_layout_data(&self) -> OpaqueStyleAndLayoutData {
|
|
let val = (*self.unsafe_get()).style_and_layout_data.get().unwrap();
|
|
(*self.unsafe_get()).style_and_layout_data.set(None);
|
|
val
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
fn text_content(&self) -> String {
|
|
if let Some(text) = self.downcast::<Text>() {
|
|
return unsafe { text.upcast().data_for_layout().to_owned() };
|
|
}
|
|
|
|
if let Some(input) = self.downcast::<HTMLInputElement>() {
|
|
return unsafe { input.value_for_layout() };
|
|
}
|
|
|
|
if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
|
|
return unsafe { area.get_value_for_layout() };
|
|
}
|
|
|
|
panic!("not text!")
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
fn selection(&self) -> Option<Range<usize>> {
|
|
if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
|
|
return unsafe { area.selection_for_layout() };
|
|
}
|
|
|
|
if let Some(input) = self.downcast::<HTMLInputElement>() {
|
|
return unsafe { input.selection_for_layout() };
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
fn image_url(&self) -> Option<ServoUrl> {
|
|
unsafe {
|
|
self.downcast::<HTMLImageElement>()
|
|
.expect("not an image!")
|
|
.image_url()
|
|
}
|
|
}
|
|
|
|
fn canvas_data(&self) -> Option<HTMLCanvasData> {
|
|
self.downcast::<HTMLCanvasElement>()
|
|
.map(|canvas| canvas.data())
|
|
}
|
|
|
|
fn svg_data(&self) -> Option<SVGSVGData> {
|
|
self.downcast::<SVGSVGElement>()
|
|
.map(|svg| svg.data())
|
|
}
|
|
|
|
fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId> {
|
|
let iframe_element = self.downcast::<HTMLIFrameElement>()
|
|
.expect("not an iframe element!");
|
|
iframe_element.browsing_context_id()
|
|
}
|
|
|
|
fn iframe_pipeline_id(&self) -> Option<PipelineId> {
|
|
let iframe_element = self.downcast::<HTMLIFrameElement>()
|
|
.expect("not an iframe element!");
|
|
iframe_element.pipeline_id()
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
fn opaque(&self) -> OpaqueNode {
|
|
unsafe {
|
|
OpaqueNode(self.get_jsobject() as usize)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Iteration and traversal
|
|
//
|
|
|
|
pub struct FollowingNodeIterator {
|
|
current: Option<DomRoot<Node>>,
|
|
root: DomRoot<Node>,
|
|
}
|
|
|
|
impl FollowingNodeIterator {
|
|
/// Skips iterating the children of the current node
|
|
pub fn next_skipping_children(&mut self) -> Option<DomRoot<Node>> {
|
|
let current = match self.current.take() {
|
|
None => return None,
|
|
Some(current) => current,
|
|
};
|
|
|
|
self.next_skipping_children_impl(current)
|
|
}
|
|
|
|
fn next_skipping_children_impl(&mut self, current: DomRoot<Node>) -> Option<DomRoot<Node>> {
|
|
if self.root == current {
|
|
self.current = None;
|
|
return None;
|
|
}
|
|
|
|
if let Some(next_sibling) = current.GetNextSibling() {
|
|
self.current = Some(next_sibling);
|
|
return current.GetNextSibling()
|
|
}
|
|
|
|
for ancestor in current.inclusive_ancestors() {
|
|
if self.root == ancestor {
|
|
break;
|
|
}
|
|
if let Some(next_sibling) = ancestor.GetNextSibling() {
|
|
self.current = Some(next_sibling);
|
|
return ancestor.GetNextSibling()
|
|
}
|
|
}
|
|
self.current = None;
|
|
None
|
|
}
|
|
}
|
|
|
|
impl Iterator for FollowingNodeIterator {
|
|
type Item = DomRoot<Node>;
|
|
|
|
// https://dom.spec.whatwg.org/#concept-tree-following
|
|
fn next(&mut self) -> Option<DomRoot<Node>> {
|
|
let current = match self.current.take() {
|
|
None => return None,
|
|
Some(current) => current,
|
|
};
|
|
|
|
if let Some(first_child) = current.GetFirstChild() {
|
|
self.current = Some(first_child);
|
|
return current.GetFirstChild()
|
|
}
|
|
|
|
self.next_skipping_children_impl(current)
|
|
}
|
|
}
|
|
|
|
pub struct PrecedingNodeIterator {
|
|
current: Option<DomRoot<Node>>,
|
|
root: DomRoot<Node>,
|
|
}
|
|
|
|
impl Iterator for PrecedingNodeIterator {
|
|
type Item = DomRoot<Node>;
|
|
|
|
// https://dom.spec.whatwg.org/#concept-tree-preceding
|
|
fn next(&mut self) -> Option<DomRoot<Node>> {
|
|
let current = match self.current.take() {
|
|
None => return None,
|
|
Some(current) => current,
|
|
};
|
|
|
|
self.current = if self.root == current {
|
|
None
|
|
} else if let Some(previous_sibling) = current.GetPreviousSibling() {
|
|
if self.root == previous_sibling {
|
|
None
|
|
} else if let Some(last_child) = previous_sibling.descending_last_children().last() {
|
|
Some(last_child)
|
|
} else {
|
|
Some(previous_sibling)
|
|
}
|
|
} else {
|
|
current.GetParentNode()
|
|
};
|
|
self.current.clone()
|
|
}
|
|
}
|
|
|
|
struct SimpleNodeIterator<I>
|
|
where I: Fn(&Node) -> Option<DomRoot<Node>>
|
|
{
|
|
current: Option<DomRoot<Node>>,
|
|
next_node: I,
|
|
}
|
|
|
|
impl<I> Iterator for SimpleNodeIterator<I>
|
|
where I: Fn(&Node) -> Option<DomRoot<Node>>
|
|
{
|
|
type Item = DomRoot<Node>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
let current = self.current.take();
|
|
self.current = current.as_ref().and_then(|c| (self.next_node)(c));
|
|
current
|
|
}
|
|
}
|
|
|
|
pub struct TreeIterator {
|
|
current: Option<DomRoot<Node>>,
|
|
depth: usize,
|
|
}
|
|
|
|
impl TreeIterator {
|
|
fn new(root: &Node) -> TreeIterator {
|
|
TreeIterator {
|
|
current: Some(DomRoot::from_ref(root)),
|
|
depth: 0,
|
|
}
|
|
}
|
|
|
|
pub fn next_skipping_children(&mut self) -> Option<DomRoot<Node>> {
|
|
let current = match self.current.take() {
|
|
None => return None,
|
|
Some(current) => current,
|
|
};
|
|
|
|
self.next_skipping_children_impl(current)
|
|
}
|
|
|
|
fn next_skipping_children_impl(&mut self, current: DomRoot<Node>) -> Option<DomRoot<Node>> {
|
|
for ancestor in current.inclusive_ancestors() {
|
|
if self.depth == 0 {
|
|
break;
|
|
}
|
|
if let Some(next_sibling) = ancestor.GetNextSibling() {
|
|
self.current = Some(next_sibling);
|
|
return Some(current);
|
|
}
|
|
self.depth -= 1;
|
|
}
|
|
debug_assert!(self.depth == 0);
|
|
self.current = None;
|
|
Some(current)
|
|
}
|
|
}
|
|
|
|
impl Iterator for TreeIterator {
|
|
type Item = DomRoot<Node>;
|
|
|
|
// https://dom.spec.whatwg.org/#concept-tree-order
|
|
fn next(&mut self) -> Option<DomRoot<Node>> {
|
|
let current = match self.current.take() {
|
|
None => return None,
|
|
Some(current) => current,
|
|
};
|
|
if let Some(first_child) = current.GetFirstChild() {
|
|
self.current = Some(first_child);
|
|
self.depth += 1;
|
|
return Some(current);
|
|
};
|
|
|
|
self.next_skipping_children_impl(current)
|
|
}
|
|
}
|
|
|
|
/// Specifies whether children must be recursively cloned or not.
|
|
#[derive(Clone, Copy, HeapSizeOf, 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>(
|
|
node: Box<N>,
|
|
document: &Document,
|
|
wrap_fn: unsafe extern "Rust" fn(*mut JSContext, &GlobalScope, Box<N>) -> DomRoot<N>)
|
|
-> DomRoot<N>
|
|
where N: DerivedFrom<Node> + DomObject
|
|
{
|
|
let window = document.window();
|
|
reflect_dom_object(node, window, wrap_fn)
|
|
}
|
|
|
|
pub fn new_inherited(doc: &Document) -> Node {
|
|
Node::new_(NodeFlags::new(), Some(doc))
|
|
}
|
|
|
|
#[allow(unrooted_must_root)]
|
|
pub fn new_document_node() -> Node {
|
|
Node::new_(NodeFlags::new() | IS_IN_DOC, None)
|
|
}
|
|
|
|
#[allow(unrooted_must_root)]
|
|
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: MutNullableDom::new(doc),
|
|
child_list: Default::default(),
|
|
children_count: Cell::new(0u32),
|
|
flags: Cell::new(flags),
|
|
inclusive_descendants_version: Cell::new(0),
|
|
ranges: WeakRangeVec::new(),
|
|
|
|
style_and_layout_data: Cell::new(None),
|
|
|
|
mutation_observers: Default::default(),
|
|
|
|
unique_id: UniqueId::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();
|
|
// Step 3.
|
|
if &*old_doc != document {
|
|
// Step 3.1.
|
|
for descendant in node.traverse_preorder() {
|
|
descendant.set_owner_doc(document);
|
|
}
|
|
for descendant in node.traverse_preorder().filter_map(|d| d.as_custom_element()) {
|
|
// Step 3.2.
|
|
ScriptThread::enqueue_callback_reaction(&*descendant,
|
|
CallbackReaction::Adopted(old_doc.clone(), DomRoot::from_ref(document)), None);
|
|
}
|
|
for descendant in node.traverse_preorder() {
|
|
// Step 3.3.
|
|
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.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 !parent.child_elements().next().is_none() {
|
|
return Err(Error::HierarchyRequest);
|
|
}
|
|
if let Some(child) = child {
|
|
if child.inclusively_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 !parent.child_elements().next().is_none() {
|
|
return Err(Error::HierarchyRequest);
|
|
}
|
|
if let Some(ref child) = child {
|
|
if child.inclusively_following_siblings()
|
|
.any(|child| child.is_doctype()) {
|
|
return Err(Error::HierarchyRequest);
|
|
}
|
|
}
|
|
},
|
|
// Step 6.3
|
|
NodeTypeId::DocumentType => {
|
|
if parent.children()
|
|
.any(|c| c.is_doctype())
|
|
{
|
|
return Err(Error::HierarchyRequest);
|
|
}
|
|
match child {
|
|
Some(child) => {
|
|
if parent.children()
|
|
.take_while(|c| &**c != child)
|
|
.any(|c| c.is::<Element>())
|
|
{
|
|
return Err(Error::HierarchyRequest);
|
|
}
|
|
},
|
|
None => {
|
|
if !parent.child_elements().next().is_none() {
|
|
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<DomRoot<Node>> {
|
|
// Step 1.
|
|
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);
|
|
|
|
// Step 5.
|
|
Node::insert(node, parent, reference_child, SuppressObserver::Unsuppressed);
|
|
|
|
// Step 6.
|
|
Ok(DomRoot::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()));
|
|
|
|
// Step 1.
|
|
let count = if node.is::<DocumentFragment>() {
|
|
node.children_count()
|
|
} else {
|
|
1
|
|
};
|
|
// Step 2.
|
|
if let Some(child) = child {
|
|
if !parent.ranges.is_empty() {
|
|
let index = child.index();
|
|
// Steps 2.1-2.
|
|
parent.ranges.increase_above(parent, index, count);
|
|
}
|
|
}
|
|
rooted_vec!(let mut new_nodes);
|
|
let new_nodes = if let NodeTypeId::DocumentFragment = node.type_id() {
|
|
// Step 3.
|
|
new_nodes.extend(node.children().map(|kid| Dom::from_ref(&*kid)));
|
|
// Step 4.
|
|
for kid in new_nodes.r() {
|
|
Node::remove(*kid, node, SuppressObserver::Suppressed);
|
|
}
|
|
// Step 5.
|
|
vtable_for(&node).children_changed(&ChildrenMutation::replace_all(new_nodes.r(), &[]));
|
|
|
|
let mutation = Mutation::ChildList {
|
|
added: None,
|
|
removed: Some(new_nodes.r()),
|
|
prev: None,
|
|
next: None,
|
|
};
|
|
MutationObserver::queue_a_mutation_record(&node, mutation);
|
|
|
|
new_nodes.r()
|
|
} else {
|
|
// Step 3.
|
|
ref_slice(&node)
|
|
};
|
|
// Step 6.
|
|
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.7.
|
|
for descendant in kid.traverse_preorder().filter_map(DomRoot::downcast::<Element>) {
|
|
// Step 7.7.2.
|
|
if descendant.is_connected() {
|
|
if descendant.get_custom_element_definition().is_some() {
|
|
// Step 7.7.2.1.
|
|
ScriptThread::enqueue_callback_reaction(&*descendant, CallbackReaction::Connected, None);
|
|
} else {
|
|
// Step 7.7.2.2.
|
|
try_upgrade_element(&*descendant);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if let SuppressObserver::Unsuppressed = suppress_observers {
|
|
vtable_for(&parent).children_changed(
|
|
&ChildrenMutation::insert(previous_sibling.r(), new_nodes, child));
|
|
|
|
let mutation = Mutation::ChildList {
|
|
added: Some(new_nodes),
|
|
removed: None,
|
|
prev: previous_sibling.r(),
|
|
next: child,
|
|
};
|
|
MutationObserver::queue_a_mutation_record(&parent, mutation);
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
rooted_vec!(let removed_nodes <- parent.children());
|
|
// Step 3.
|
|
rooted_vec!(let mut added_nodes);
|
|
let added_nodes = if let Some(node) = node.as_ref() {
|
|
if let NodeTypeId::DocumentFragment = node.type_id() {
|
|
added_nodes.extend(node.children().map(|child| Dom::from_ref(&*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.
|
|
vtable_for(&parent).children_changed(
|
|
&ChildrenMutation::replace_all(removed_nodes.r(), added_nodes));
|
|
|
|
if !removed_nodes.is_empty() || !added_nodes.is_empty() {
|
|
let mutation = Mutation::ChildList {
|
|
added: Some(added_nodes),
|
|
removed: Some(removed_nodes.r()),
|
|
prev: None,
|
|
next: None,
|
|
};
|
|
MutationObserver::queue_a_mutation_record(&parent, mutation);
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#concept-node-pre-remove
|
|
fn pre_remove(child: &Node, parent: &Node) -> Fallible<DomRoot<Node>> {
|
|
// Step 1.
|
|
match child.GetParentNode() {
|
|
Some(ref node) if &**node != parent => return Err(Error::NotFound),
|
|
None => return Err(Error::NotFound),
|
|
_ => ()
|
|
}
|
|
|
|
// Step 2.
|
|
Node::remove(child, parent, SuppressObserver::Unsuppressed);
|
|
|
|
// Step 3.
|
|
Ok(DomRoot::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 == parent));
|
|
let cached_index = {
|
|
if parent.ranges.is_empty() {
|
|
None
|
|
} else {
|
|
// Step 1.
|
|
let index = node.index();
|
|
// Steps 2-3 are handled in Node::unbind_from_tree.
|
|
// Steps 4-5.
|
|
parent.ranges.decrease_above(parent, index, 1);
|
|
// Parent had ranges, we needed the index, let's keep track of
|
|
// it to avoid computing it for other ranges when calling
|
|
// unbind_from_tree recursively.
|
|
Some(index)
|
|
}
|
|
};
|
|
// Step 6. pre-removing steps for node iterators
|
|
// Step 7.
|
|
let old_previous_sibling = node.GetPreviousSibling();
|
|
// Step 8.
|
|
let old_next_sibling = node.GetNextSibling();
|
|
// Steps 9-10 are handled in unbind_from_tree.
|
|
parent.remove_child(node, cached_index);
|
|
// Step 11. transient registered observers
|
|
// Step 12.
|
|
if let SuppressObserver::Unsuppressed = suppress_observers {
|
|
vtable_for(&parent).children_changed(
|
|
&ChildrenMutation::replace(old_previous_sibling.r(),
|
|
&Some(&node), &[],
|
|
old_next_sibling.r()));
|
|
|
|
let removed = [node];
|
|
let mutation = Mutation::ChildList {
|
|
added: None,
|
|
removed: Some(&removed),
|
|
prev: old_previous_sibling.r(),
|
|
next: old_next_sibling.r(),
|
|
};
|
|
MutationObserver::queue_a_mutation_record(&parent, mutation);
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#concept-node-clone
|
|
pub fn clone(node: &Node, maybe_doc: Option<&Document>,
|
|
clone_children: CloneChildrenFlag) -> DomRoot<Node> {
|
|
// Step 1.
|
|
let document = match maybe_doc {
|
|
Some(doc) => DomRoot::from_ref(doc),
|
|
None => node.owner_doc()
|
|
};
|
|
|
|
// Step 2.
|
|
// XXXabinader: clone() for each node as trait?
|
|
let copy: DomRoot<Node> = match node.type_id() {
|
|
NodeTypeId::DocumentType => {
|
|
let doctype = node.downcast::<DocumentType>().unwrap();
|
|
let doctype = DocumentType::new(doctype.name().clone(),
|
|
Some(doctype.public_id().clone()),
|
|
Some(doctype.system_id().clone()),
|
|
&document);
|
|
DomRoot::upcast::<Node>(doctype)
|
|
},
|
|
NodeTypeId::DocumentFragment => {
|
|
let doc_fragment = DocumentFragment::new(&document);
|
|
DomRoot::upcast::<Node>(doc_fragment)
|
|
},
|
|
NodeTypeId::CharacterData(_) => {
|
|
let cdata = node.downcast::<CharacterData>().unwrap();
|
|
cdata.clone_with_data(cdata.Data(), &document)
|
|
},
|
|
NodeTypeId::Document(_) => {
|
|
let document = node.downcast::<Document>().unwrap();
|
|
let is_html_doc = if document.is_html_document() {
|
|
IsHTMLDocument::HTMLDocument
|
|
} else {
|
|
IsHTMLDocument::NonHTMLDocument
|
|
};
|
|
let window = document.window();
|
|
let loader = DocumentLoader::new(&*document.loader());
|
|
let document = Document::new(window, HasBrowsingContext::No,
|
|
Some(document.url()),
|
|
// https://github.com/whatwg/dom/issues/378
|
|
document.origin().clone(),
|
|
is_html_doc, None,
|
|
None, DocumentActivity::Inactive,
|
|
DocumentSource::NotFromParser, loader,
|
|
None, None);
|
|
DomRoot::upcast::<Node>(document)
|
|
},
|
|
NodeTypeId::Element(..) => {
|
|
let element = node.downcast::<Element>().unwrap();
|
|
let name = QualName {
|
|
prefix: element.prefix().as_ref().map(|p| Prefix::from(&**p)),
|
|
ns: element.namespace().clone(),
|
|
local: element.local_name().clone()
|
|
};
|
|
let element = Element::create(name,
|
|
element.get_is(),
|
|
&document,
|
|
ElementCreator::ScriptCreated,
|
|
CustomElementCreationMode::Asynchronous);
|
|
DomRoot::upcast::<Node>(element)
|
|
},
|
|
};
|
|
|
|
// Step 3.
|
|
let document = match copy.downcast::<Document>() {
|
|
Some(doc) => DomRoot::from_ref(doc),
|
|
None => DomRoot::from_ref(&*document),
|
|
};
|
|
assert!(copy.owner_doc() == document);
|
|
|
|
// Step 4 (some data already copied in step 2).
|
|
match node.type_id() {
|
|
NodeTypeId::Document(_) => {
|
|
let node_doc = node.downcast::<Document>().unwrap();
|
|
let copy_doc = copy.downcast::<Document>().unwrap();
|
|
copy_doc.set_encoding(node_doc.encoding());
|
|
copy_doc.set_quirks_mode(node_doc.quirks_mode());
|
|
},
|
|
NodeTypeId::Element(..) => {
|
|
let node_elem = node.downcast::<Element>().unwrap();
|
|
let copy_elem = copy.downcast::<Element>().unwrap();
|
|
|
|
for attr in node_elem.attrs().iter() {
|
|
copy_elem.push_new_attribute(attr.local_name().clone(),
|
|
attr.value().clone(),
|
|
attr.name().clone(),
|
|
attr.namespace().clone(),
|
|
attr.prefix().cloned());
|
|
}
|
|
},
|
|
_ => ()
|
|
}
|
|
|
|
// Step 5: cloning steps.
|
|
vtable_for(&node).cloning_steps(©, maybe_doc, clone_children);
|
|
|
|
// Step 6.
|
|
if clone_children == CloneChildrenFlag::CloneChildren {
|
|
for child in node.children() {
|
|
let child_copy = Node::clone(&child, Some(&document),
|
|
clone_children);
|
|
let _inserted_node = Node::pre_insert(&child_copy, ©, None);
|
|
}
|
|
}
|
|
|
|
// Step 7.
|
|
copy
|
|
}
|
|
|
|
/// https://html.spec.whatwg.org/multipage/#child-text-content
|
|
pub fn child_text_content(&self) -> DOMString {
|
|
Node::collect_text_contents(self.children())
|
|
}
|
|
|
|
pub fn collect_text_contents<T: Iterator<Item=DomRoot<Node>>>(iterator: T) -> DOMString {
|
|
let mut content = String::new();
|
|
for node in iterator {
|
|
if let Some(ref text) = node.downcast::<Text>() {
|
|
content.push_str(&text.upcast::<CharacterData>().data());
|
|
}
|
|
}
|
|
DOMString::from(content)
|
|
}
|
|
|
|
pub fn namespace_to_string(namespace: Namespace) -> Option<DOMString> {
|
|
match namespace {
|
|
ns!() => None,
|
|
// FIXME(ajeffrey): convert directly from Namespace to DOMString
|
|
_ => Some(DOMString::from(&*namespace))
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#locate-a-namespace
|
|
pub fn locate_namespace(node: &Node, prefix: Option<DOMString>) -> Namespace {
|
|
match node.type_id() {
|
|
NodeTypeId::Element(_) => {
|
|
node.downcast::<Element>().unwrap().locate_namespace(prefix)
|
|
},
|
|
NodeTypeId::Document(_) => {
|
|
node.downcast::<Document>().unwrap()
|
|
.GetDocumentElement().as_ref()
|
|
.map_or(ns!(), |elem| elem.locate_namespace(prefix))
|
|
},
|
|
NodeTypeId::DocumentType | NodeTypeId::DocumentFragment => ns!(),
|
|
_ => {
|
|
node.GetParentElement().as_ref()
|
|
.map_or(ns!(), |elem| elem.locate_namespace(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(..) => {
|
|
self.downcast::<Element>().unwrap().TagName()
|
|
}
|
|
NodeTypeId::CharacterData(CharacterDataTypeId::Text) => DOMString::from("#text"),
|
|
NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
|
|
self.downcast::<ProcessingInstruction>().unwrap().Target()
|
|
}
|
|
NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => DOMString::from("#comment"),
|
|
NodeTypeId::DocumentType => {
|
|
self.downcast::<DocumentType>().unwrap().name().clone()
|
|
},
|
|
NodeTypeId::DocumentFragment => DOMString::from("#document-fragment"),
|
|
NodeTypeId::Document(_) => DOMString::from("#document")
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-baseuri
|
|
fn BaseURI(&self) -> USVString {
|
|
USVString(String::from(self.owner_doc().base_url().as_str()))
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-ownerdocument
|
|
fn GetOwnerDocument(&self) -> Option<DomRoot<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-getrootnode
|
|
fn GetRootNode(&self) -> DomRoot<Node> {
|
|
self.inclusive_ancestors().last().unwrap()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-parentnode
|
|
fn GetParentNode(&self) -> Option<DomRoot<Node>> {
|
|
self.parent_node.get()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-parentelement
|
|
fn GetParentElement(&self) -> Option<DomRoot<Element>> {
|
|
self.GetParentNode().and_then(DomRoot::downcast)
|
|
}
|
|
|
|
// 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) -> DomRoot<NodeList> {
|
|
self.child_list.or_init(|| {
|
|
let doc = self.owner_doc();
|
|
let window = doc.window();
|
|
NodeList::new_child_list(window, self)
|
|
})
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-firstchild
|
|
fn GetFirstChild(&self) -> Option<DomRoot<Node>> {
|
|
self.first_child.get()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-lastchild
|
|
fn GetLastChild(&self) -> Option<DomRoot<Node>> {
|
|
self.last_child.get()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-previoussibling
|
|
fn GetPreviousSibling(&self) -> Option<DomRoot<Node>> {
|
|
self.prev_sibling.get()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-nextsibling
|
|
fn GetNextSibling(&self) -> Option<DomRoot<Node>> {
|
|
self.next_sibling.get()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-nodevalue
|
|
fn GetNodeValue(&self) -> Option<DOMString> {
|
|
self.downcast::<CharacterData>().map(CharacterData::Data)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-nodevalue
|
|
fn SetNodeValue(&self, val: Option<DOMString>) {
|
|
if let Some(character_data) = self.downcast::<CharacterData>() {
|
|
character_data.SetData(val.unwrap_or_default());
|
|
}
|
|
}
|
|
|
|
// 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 = self.downcast::<CharacterData>().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_default();
|
|
match self.type_id() {
|
|
NodeTypeId::DocumentFragment |
|
|
NodeTypeId::Element(..) => {
|
|
// Step 1-2.
|
|
let node = if value.is_empty() {
|
|
None
|
|
} else {
|
|
Some(DomRoot::upcast(self.owner_doc().CreateTextNode(value)))
|
|
};
|
|
|
|
// Step 3.
|
|
Node::replace_all(node.r(), self);
|
|
}
|
|
NodeTypeId::CharacterData(..) => {
|
|
let characterdata = self.downcast::<CharacterData>().unwrap();
|
|
characterdata.SetData(value);
|
|
}
|
|
NodeTypeId::DocumentType |
|
|
NodeTypeId::Document(_) => {}
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-insertbefore
|
|
fn InsertBefore(&self, node: &Node, child: Option<&Node>) -> Fallible<DomRoot<Node>> {
|
|
Node::pre_insert(node, self, child)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-appendchild
|
|
fn AppendChild(&self, node: &Node) -> Fallible<DomRoot<Node>> {
|
|
Node::pre_insert(node, self, None)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#concept-node-replace
|
|
fn ReplaceChild(&self, node: &Node, child: &Node) -> Fallible<DomRoot<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| c.upcast::<Node>() != 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| c.upcast::<Node>() != 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 != child)
|
|
{
|
|
return Err(Error::HierarchyRequest);
|
|
}
|
|
if self.children()
|
|
.take_while(|c| &**c != child)
|
|
.any(|c| c.is::<Element>())
|
|
{
|
|
return Err(Error::HierarchyRequest);
|
|
}
|
|
},
|
|
NodeTypeId::CharacterData(..) => (),
|
|
NodeTypeId::Document(_) => unreachable!(),
|
|
}
|
|
}
|
|
|
|
// 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 previous_sibling = child.GetPreviousSibling();
|
|
|
|
// Step 10.
|
|
let document = document_from_node(self);
|
|
Node::adopt(node, &document);
|
|
|
|
let removed_child = if node != child {
|
|
// Step 11.
|
|
Node::remove(child, self, SuppressObserver::Suppressed);
|
|
Some(child)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Step 12.
|
|
rooted_vec!(let mut nodes);
|
|
let nodes = if node.type_id() == NodeTypeId::DocumentFragment {
|
|
nodes.extend(node.children().map(|node| Dom::from_ref(&*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(),
|
|
&removed_child, nodes,
|
|
reference_child));
|
|
let removed = removed_child.map(|r| [r]);
|
|
let mutation = Mutation::ChildList {
|
|
added: Some(nodes),
|
|
removed: removed.as_ref().map(|r| &r[..]),
|
|
prev: previous_sibling.r(),
|
|
next: reference_child,
|
|
};
|
|
MutationObserver::queue_a_mutation_record(&self, mutation);
|
|
|
|
// Step 15.
|
|
Ok(DomRoot::from_ref(child))
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-removechild
|
|
fn RemoveChild(&self, node: &Node)
|
|
-> Fallible<DomRoot<Node>> {
|
|
Node::pre_remove(node, self)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-normalize
|
|
fn Normalize(&self) {
|
|
let mut children = self.children().enumerate().peekable();
|
|
while let Some((_, node)) = children.next() {
|
|
if let Some(text) = node.downcast::<Text>() {
|
|
let cdata = text.upcast::<CharacterData>();
|
|
let mut length = cdata.Length();
|
|
if length == 0 {
|
|
Node::remove(&node, self, SuppressObserver::Unsuppressed);
|
|
continue;
|
|
}
|
|
while children.peek().map_or(false, |&(_, ref sibling)| sibling.is::<Text>()) {
|
|
let (index, sibling) = children.next().unwrap();
|
|
sibling.ranges.drain_to_preceding_text_sibling(&sibling, &node, length);
|
|
self.ranges.move_to_text_child_at(self, index as u32, &node, length as u32);
|
|
let sibling_cdata = sibling.downcast::<CharacterData>().unwrap();
|
|
length += sibling_cdata.Length();
|
|
cdata.append_data(&sibling_cdata.data());
|
|
Node::remove(&sibling, self, SuppressObserver::Unsuppressed);
|
|
}
|
|
} else {
|
|
node.Normalize();
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-clonenode
|
|
fn CloneNode(&self, deep: bool) -> DomRoot<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 = node.downcast::<DocumentType>().unwrap();
|
|
let other_doctype = other.downcast::<DocumentType>().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 = node.downcast::<Element>().unwrap();
|
|
let other_element = other.downcast::<Element>().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 = node.downcast::<ProcessingInstruction>().unwrap();
|
|
let other_pi = other.downcast::<ProcessingInstruction>().unwrap();
|
|
(*pi.target() == *other_pi.target()) &&
|
|
(*pi.upcast::<CharacterData>().data() == *other_pi.upcast::<CharacterData>().data())
|
|
}
|
|
fn is_equal_characterdata(node: &Node, other: &Node) -> bool {
|
|
let characterdata = node.downcast::<CharacterData>().unwrap();
|
|
let other_characterdata = other.downcast::<CharacterData>().unwrap();
|
|
*characterdata.data() == *other_characterdata.data()
|
|
}
|
|
fn is_equal_element_attrs(node: &Node, other: &Node) -> bool {
|
|
let element = node.downcast::<Element>().unwrap();
|
|
let other_element = other.downcast::<Element>().unwrap();
|
|
assert!(element.attrs().len() == other_element.attrs().len());
|
|
element.attrs().iter().all(|attr| {
|
|
other_element.attrs().iter().any(|other_attr| {
|
|
(*attr.namespace() == *other_attr.namespace()) &&
|
|
(attr.local_name() == other_attr.local_name()) &&
|
|
(**attr.value() == **other_attr.value())
|
|
})
|
|
})
|
|
}
|
|
fn is_equal_node(this: &Node, node: &Node) -> bool {
|
|
// Step 2.
|
|
if this.NodeType() != node.NodeType() {
|
|
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, &other_child)
|
|
})
|
|
}
|
|
match maybe_node {
|
|
// Step 1.
|
|
None => false,
|
|
// Step 2-6.
|
|
Some(node) => is_equal_node(self, node)
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-issamenode
|
|
fn IsSameNode(&self, other_node: Option<&Node>) -> bool {
|
|
match other_node {
|
|
Some(node) => self == node,
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-node-comparedocumentposition
|
|
fn CompareDocumentPosition(&self, other: &Node) -> u16 {
|
|
// step 1.
|
|
if self == other {
|
|
return 0
|
|
}
|
|
|
|
// FIXME(emilio): This will eventually need to handle attribute nodes.
|
|
|
|
let mut self_and_ancestors =
|
|
self.inclusive_ancestors().collect::<SmallVec<[_; 20]>>();
|
|
let mut other_and_ancestors =
|
|
other.inclusive_ancestors().collect::<SmallVec<[_; 20]>>();
|
|
|
|
if self_and_ancestors.last() != other_and_ancestors.last() {
|
|
let random =
|
|
as_uintptr(self_and_ancestors.last().unwrap())
|
|
< as_uintptr(other_and_ancestors.last().unwrap());
|
|
let random = if random {
|
|
NodeConstants::DOCUMENT_POSITION_FOLLOWING
|
|
} else {
|
|
NodeConstants::DOCUMENT_POSITION_PRECEDING
|
|
};
|
|
|
|
// Disconnected.
|
|
return random +
|
|
NodeConstants::DOCUMENT_POSITION_DISCONNECTED +
|
|
NodeConstants::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
|
|
}
|
|
|
|
let mut parent = self_and_ancestors.pop().unwrap();
|
|
other_and_ancestors.pop().unwrap();
|
|
|
|
let mut current_position = cmp::min(self_and_ancestors.len(), other_and_ancestors.len());
|
|
|
|
while current_position > 0 {
|
|
current_position -= 1;
|
|
let child_1 = self_and_ancestors.pop().unwrap();
|
|
let child_2 = other_and_ancestors.pop().unwrap();
|
|
|
|
if child_1 != child_2 {
|
|
let is_before =
|
|
parent.children().position(|c| c == child_1).unwrap()
|
|
< parent.children().position(|c| c == child_2).unwrap();
|
|
// If I am before, `other` is following, and the other way
|
|
// around.
|
|
return if is_before {
|
|
NodeConstants::DOCUMENT_POSITION_FOLLOWING
|
|
} else {
|
|
NodeConstants::DOCUMENT_POSITION_PRECEDING
|
|
};
|
|
}
|
|
|
|
parent = child_1;
|
|
}
|
|
|
|
// We hit the end of one of the parent chains, so one node needs to be
|
|
// contained in the other.
|
|
//
|
|
// If we're the container, return that `other` is contained by us.
|
|
return if self_and_ancestors.len() < other_and_ancestors.len() {
|
|
NodeConstants::DOCUMENT_POSITION_FOLLOWING +
|
|
NodeConstants::DOCUMENT_POSITION_CONTAINED_BY
|
|
} else {
|
|
NodeConstants::DOCUMENT_POSITION_PRECEDING +
|
|
NodeConstants::DOCUMENT_POSITION_CONTAINS
|
|
}
|
|
}
|
|
|
|
// 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(..) => {
|
|
self.downcast::<Element>().unwrap().lookup_prefix(namespace)
|
|
},
|
|
NodeTypeId::Document(_) => {
|
|
self.downcast::<Document>().unwrap().GetDocumentElement().and_then(|element| {
|
|
element.lookup_prefix(namespace)
|
|
})
|
|
},
|
|
NodeTypeId::DocumentType | NodeTypeId::DocumentFragment => None,
|
|
_ => {
|
|
self.GetParentElement().and_then(|element| {
|
|
element.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
|
|
}
|
|
}
|
|
|
|
pub fn document_from_node<T: DerivedFrom<Node> + DomObject>(derived: &T) -> DomRoot<Document> {
|
|
derived.upcast().owner_doc()
|
|
}
|
|
|
|
pub fn window_from_node<T: DerivedFrom<Node> + DomObject>(derived: &T) -> DomRoot<Window> {
|
|
let document = document_from_node(derived);
|
|
DomRoot::from_ref(document.window())
|
|
}
|
|
|
|
impl VirtualMethods for Node {
|
|
fn super_type(&self) -> Option<&VirtualMethods> {
|
|
Some(self.upcast::<EventTarget>() as &VirtualMethods)
|
|
}
|
|
|
|
fn children_changed(&self, mutation: &ChildrenMutation) {
|
|
if let Some(ref s) = self.super_type() {
|
|
s.children_changed(mutation);
|
|
}
|
|
if let Some(list) = self.child_list.get() {
|
|
list.as_children_list().children_changed(mutation);
|
|
}
|
|
}
|
|
|
|
// This handles the ranges mentioned in steps 2-3 when removing a node.
|
|
// https://dom.spec.whatwg.org/#concept-node-remove
|
|
fn unbind_from_tree(&self, context: &UnbindContext) {
|
|
self.super_type().unwrap().unbind_from_tree(context);
|
|
self.ranges.drain_to_parent(context, self);
|
|
}
|
|
}
|
|
|
|
/// A summary of the changes that happened to a node.
|
|
#[derive(Clone, Copy, HeapSizeOf, PartialEq)]
|
|
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] },
|
|
/// Mutation for when a Text node's data is modified.
|
|
/// This doesn't change the structure of the list, which is what the other
|
|
/// variants' fields are stored for at the moment, so this can just have no
|
|
/// fields.
|
|
ChangeText,
|
|
}
|
|
|
|
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 Option<&'a Node>,
|
|
added: &'a [&'a Node],
|
|
next: Option<&'a Node>)
|
|
-> ChildrenMutation<'a> {
|
|
if let Some(ref removed) = *removed {
|
|
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,
|
|
}
|
|
}
|
|
} else {
|
|
ChildrenMutation::insert(prev, added, next)
|
|
}
|
|
}
|
|
|
|
fn replace_all(removed: &'a [&'a Node], added: &'a [&'a Node])
|
|
-> ChildrenMutation<'a> {
|
|
ChildrenMutation::ReplaceAll { removed: removed, added: added }
|
|
}
|
|
|
|
/// Get the child that follows the added or removed children.
|
|
/// Currently only used when this mutation might force us to
|
|
/// restyle later children (see HAS_SLOW_SELECTOR_LATER_SIBLINGS and
|
|
/// Element's implementation of VirtualMethods::children_changed).
|
|
pub fn next_child(&self) -> Option<&Node> {
|
|
match *self {
|
|
ChildrenMutation::Append { .. } => None,
|
|
ChildrenMutation::Insert { next, .. } => Some(next),
|
|
ChildrenMutation::Prepend { next, .. } => Some(next),
|
|
ChildrenMutation::Replace { next, .. } => next,
|
|
ChildrenMutation::ReplaceAll { .. } => None,
|
|
ChildrenMutation::ChangeText => None,
|
|
}
|
|
}
|
|
|
|
/// If nodes were added or removed at the start or end of a container, return any
|
|
/// previously-existing child whose ":first-child" or ":last-child" status *may* have changed.
|
|
///
|
|
/// NOTE: This does not check whether the inserted/removed nodes were elements, so in some
|
|
/// cases it will return a false positive. This doesn't matter for correctness, because at
|
|
/// worst the returned element will be restyled unnecessarily.
|
|
pub fn modified_edge_element(&self) -> Option<DomRoot<Node>> {
|
|
match *self {
|
|
// Add/remove at start of container: Return the first following element.
|
|
ChildrenMutation::Prepend { next, .. } |
|
|
ChildrenMutation::Replace { prev: None, next: Some(next), .. } => {
|
|
next.inclusively_following_siblings().filter(|node| node.is::<Element>()).next()
|
|
}
|
|
// Add/remove at end of container: Return the last preceding element.
|
|
ChildrenMutation::Append { prev, .. } |
|
|
ChildrenMutation::Replace { prev: Some(prev), next: None, .. } => {
|
|
prev.inclusively_preceding_siblings().filter(|node| node.is::<Element>()).next()
|
|
}
|
|
// Insert or replace in the middle:
|
|
ChildrenMutation::Insert { prev, next, .. } |
|
|
ChildrenMutation::Replace { prev: Some(prev), next: Some(next), .. } => {
|
|
if prev.inclusively_preceding_siblings().all(|node| !node.is::<Element>()) {
|
|
// Before the first element: Return the first following element.
|
|
next.inclusively_following_siblings().filter(|node| node.is::<Element>()).next()
|
|
} else if next.inclusively_following_siblings().all(|node| !node.is::<Element>()) {
|
|
// After the last element: Return the last preceding element.
|
|
prev.inclusively_preceding_siblings().filter(|node| node.is::<Element>()).next()
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
ChildrenMutation::Replace { prev: None, next: None, .. } => unreachable!(),
|
|
ChildrenMutation::ReplaceAll { .. } => None,
|
|
ChildrenMutation::ChangeText => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The context of the unbinding from a tree of a node when one of its
|
|
/// inclusive ancestors is removed.
|
|
pub struct UnbindContext<'a> {
|
|
/// The index of the inclusive ancestor that was removed.
|
|
index: Cell<Option<u32>>,
|
|
/// The parent of the inclusive ancestor that was removed.
|
|
pub parent: &'a Node,
|
|
/// The previous sibling of the inclusive ancestor that was removed.
|
|
prev_sibling: Option<&'a Node>,
|
|
/// Whether the tree is in a document.
|
|
pub tree_in_doc: bool,
|
|
}
|
|
|
|
impl<'a> UnbindContext<'a> {
|
|
/// Create a new `UnbindContext` value.
|
|
fn new(parent: &'a Node,
|
|
prev_sibling: Option<&'a Node>,
|
|
cached_index: Option<u32>) -> Self {
|
|
UnbindContext {
|
|
index: Cell::new(cached_index),
|
|
parent: parent,
|
|
prev_sibling: prev_sibling,
|
|
tree_in_doc: parent.is_in_doc(),
|
|
}
|
|
}
|
|
|
|
/// The index of the inclusive ancestor that was removed from the tree.
|
|
#[allow(unsafe_code)]
|
|
pub fn index(&self) -> u32 {
|
|
if let Some(index) = self.index.get() {
|
|
return index;
|
|
}
|
|
let index = self.prev_sibling.map_or(0, |sibling| sibling.index() + 1);
|
|
self.index.set(Some(index));
|
|
index
|
|
}
|
|
}
|
|
|
|
/// A node's unique ID, for devtools.
|
|
struct UniqueId {
|
|
cell: UnsafeCell<Option<Box<Uuid>>>,
|
|
}
|
|
|
|
unsafe_no_jsmanaged_fields!(UniqueId);
|
|
|
|
impl HeapSizeOf for UniqueId {
|
|
#[allow(unsafe_code)]
|
|
fn heap_size_of_children(&self) -> usize {
|
|
if let &Some(ref uuid) = unsafe { &*self.cell.get() } {
|
|
unsafe { heap_size_of(&** uuid as *const Uuid as *const _) }
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
impl UniqueId {
|
|
/// Create a new `UniqueId` value. The underlying `Uuid` is lazily created.
|
|
fn new() -> UniqueId {
|
|
UniqueId { cell: UnsafeCell::new(None) }
|
|
}
|
|
|
|
/// The Uuid of that unique ID.
|
|
#[allow(unsafe_code)]
|
|
fn borrow(&self) -> &Uuid {
|
|
unsafe {
|
|
let ptr = self.cell.get();
|
|
if (*ptr).is_none() {
|
|
*ptr = Some(Box::new(Uuid::new_v4()));
|
|
}
|
|
&(&*ptr).as_ref().unwrap()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Into<LayoutNodeType> for NodeTypeId {
|
|
#[inline(always)]
|
|
fn into(self) -> LayoutNodeType {
|
|
match self {
|
|
NodeTypeId::Element(e) =>
|
|
LayoutNodeType::Element(e.into()),
|
|
NodeTypeId::CharacterData(CharacterDataTypeId::Text) =>
|
|
LayoutNodeType::Text,
|
|
x => unreachable!("Layout should not traverse nodes of type {:?}", x),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Into<LayoutElementType> for ElementTypeId {
|
|
#[inline(always)]
|
|
fn into(self) -> LayoutElementType {
|
|
match self {
|
|
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLCanvasElement) =>
|
|
LayoutElementType::HTMLCanvasElement,
|
|
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement) =>
|
|
LayoutElementType::HTMLIFrameElement,
|
|
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement) =>
|
|
LayoutElementType::HTMLImageElement,
|
|
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement) =>
|
|
LayoutElementType::HTMLInputElement,
|
|
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement) =>
|
|
LayoutElementType::HTMLObjectElement,
|
|
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableCellElement(_)) =>
|
|
LayoutElementType::HTMLTableCellElement,
|
|
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableColElement) =>
|
|
LayoutElementType::HTMLTableColElement,
|
|
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableElement) =>
|
|
LayoutElementType::HTMLTableElement,
|
|
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableRowElement) =>
|
|
LayoutElementType::HTMLTableRowElement,
|
|
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableSectionElement) =>
|
|
LayoutElementType::HTMLTableSectionElement,
|
|
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement) =>
|
|
LayoutElementType::HTMLTextAreaElement,
|
|
ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement(SVGGraphicsElementTypeId::SVGSVGElement)) =>
|
|
LayoutElementType::SVGSVGElement,
|
|
_ => LayoutElementType::Element,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Helper trait to insert an element into vector whose elements
|
|
/// are maintained in tree order
|
|
pub trait VecPreOrderInsertionHelper<T> {
|
|
fn insert_pre_order(&mut self, elem: &T, tree_root: &Node);
|
|
}
|
|
|
|
impl<T> VecPreOrderInsertionHelper<T> for Vec<Dom<T>>
|
|
where T: DerivedFrom<Node> + DomObject
|
|
{
|
|
/// This algorithm relies on the following assumptions:
|
|
/// * any elements inserted in this vector share the same tree root
|
|
/// * any time an element is removed from the tree root, it is also removed from this array
|
|
/// * any time an element is moved within the tree, it is removed from this array and re-inserted
|
|
///
|
|
/// Under these assumptions, an element's tree-order position in this array can be determined by
|
|
/// performing a [preorder traversal](https://dom.spec.whatwg.org/#concept-tree-order) of the tree root's children,
|
|
/// and increasing the destination index in the array every time a node in the array is encountered during
|
|
/// the traversal.
|
|
fn insert_pre_order(&mut self, elem: &T, tree_root: &Node) {
|
|
if self.is_empty() {
|
|
self.push(Dom::from_ref(elem));
|
|
return;
|
|
}
|
|
|
|
let elem_node = elem.upcast::<Node>();
|
|
let mut head: usize = 0;
|
|
for node in tree_root.traverse_preorder() {
|
|
let head_node = DomRoot::upcast::<Node>(DomRoot::from_ref(&*self[head]));
|
|
if head_node == node {
|
|
head += 1;
|
|
}
|
|
if elem_node == node.r() || head == self.len() {
|
|
break;
|
|
}
|
|
}
|
|
self.insert(head, Dom::from_ref(elem));
|
|
}
|
|
}
|