/* 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 https://mozilla.org/MPL/2.0/. */ //! The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements. use std::borrow::Cow; use std::cell::{Cell, LazyCell, UnsafeCell}; use std::default::Default; use std::f64::consts::PI; use std::ops::Range; use std::slice::from_ref; use std::sync::Arc as StdArc; use std::{cmp, fmt, iter}; use app_units::Au; use base::id::{BrowsingContextId, PipelineId}; use bitflags::bitflags; use devtools_traits::NodeInfo; use dom_struct::dom_struct; use embedder_traits::UntrustedNodeAddress; use euclid::default::{Rect, Size2D, Vector2D}; use html5ever::serialize::HtmlSerializer; use html5ever::{Namespace, Prefix, QualName, ns, serialize as html_serialize}; use js::jsapi::JSObject; use js::rust::HandleObject; use libc::{self, c_void, uintptr_t}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use pixels::{Image, ImageMetadata}; use script_bindings::codegen::InheritTypes::DocumentFragmentTypeId; use script_layout_interface::{ GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType, QueryMsg, SVGSVGData, StyleData, TrustedNodeAddress, }; use script_traits::DocumentActivity; use selectors::matching::{ MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, matches_selector_list, }; use selectors::parser::SelectorList; use servo_arc::Arc; use servo_config::pref; use servo_url::ServoUrl; use smallvec::SmallVec; use style::context::QuirksMode; use style::dom::OpaqueNode; use style::properties::ComputedValues; use style::selector_parser::{SelectorImpl, SelectorParser}; use style::stylesheets::{Stylesheet, UrlExtraData}; use uuid::Uuid; use xml5ever::serialize as xml_serialize; use crate::conversions::Convert; use crate::document_loader::DocumentLoader; use crate::dom::attr::Attr; use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut}; use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods; use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::{ GetRootNodeOptions, NodeConstants, NodeMethods, }; use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; use crate::dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{ ShadowRootMode, SlotAssignmentMode, }; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::UnionTypes::NodeOrString; use crate::dom::bindings::conversions::{self, DerivedFrom}; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::{ Castable, CharacterDataTypeId, ElementTypeId, EventTargetTypeId, HTMLElementTypeId, NodeTypeId, SVGElementTypeId, SVGGraphicsElementTypeId, TextTypeId, }; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{DomObject, DomObjectWrap, reflect_dom_object_with_proto}; use crate::dom::bindings::root::{Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom, ToLayout}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::xmlname::namespace_from_domstring; use crate::dom::characterdata::{CharacterData, LayoutCharacterDataHelpers}; use crate::dom::cssstylesheet::CSSStyleSheet; use crate::dom::customelementregistry::{CallbackReaction, try_upgrade_element}; use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument}; use crate::dom::documentfragment::DocumentFragment; use crate::dom::documenttype::DocumentType; use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator, SelectorWrapper}; use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlbodyelement::HTMLBodyElement; use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutHTMLCanvasElementHelpers}; use crate::dom::htmlcollection::HTMLCollection; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods}; use crate::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers}; use crate::dom::htmlinputelement::{HTMLInputElement, InputType, LayoutHTMLInputElementHelpers}; use crate::dom::htmllinkelement::HTMLLinkElement; use crate::dom::htmlslotelement::{HTMLSlotElement, Slottable}; use crate::dom::htmlstyleelement::HTMLStyleElement; use crate::dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; use crate::dom::htmlvideoelement::{HTMLVideoElement, LayoutHTMLVideoElementHelpers}; use crate::dom::mutationobserver::{Mutation, MutationObserver, RegisteredObserver}; use crate::dom::nodelist::NodeList; use crate::dom::pointerevent::{PointerEvent, PointerId}; use crate::dom::processinginstruction::ProcessingInstruction; use crate::dom::range::WeakRangeVec; use crate::dom::raredata::NodeRareData; use crate::dom::servoparser::{ServoParser, serialize_html_fragment}; use crate::dom::shadowroot::{IsUserAgentWidget, LayoutShadowRootHelpers, ShadowRoot}; use crate::dom::stylesheetlist::StyleSheetListOwner; use crate::dom::svgsvgelement::{LayoutSVGSVGElementHelpers, SVGSVGElement}; use crate::dom::text::Text; use crate::dom::virtualmethods::{VirtualMethods, vtable_for}; use crate::dom::window::Window; use crate::script_runtime::CanGc; use crate::script_thread::ScriptThread; // // 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, /// The first child of this node. first_child: MutNullableDom, /// The last child of this node. last_child: MutNullableDom, /// The next sibling of this node. next_sibling: MutNullableDom, /// The previous sibling of this node. prev_sibling: MutNullableDom, /// The document that this node belongs to. owner_doc: MutNullableDom, /// Rare node data. rare_data: DomRefCell>>, /// The live count of children of this node. children_count: Cell, /// A bitfield of flags for node items. flags: Cell, /// The maximum version of any inclusive descendant of this node. inclusive_descendants_version: Cell, /// Style data for this node. This is accessed and mutated by style /// passes and is used to lay out this node and populate layout data. #[no_trace] style_data: DomRefCell>>, /// Layout data for this node. This is populated during layout and can /// be used for incremental relayout and script queries. #[no_trace] layout_data: DomRefCell>>, } impl fmt::Debug for Node { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if matches!(self.type_id(), NodeTypeId::Element(_)) { let el = self.downcast::().unwrap(); el.fmt(f) } else { write!(f, "[Node({:?})]", self.type_id()) } } } /// Flags for node items #[derive(Clone, Copy, JSTraceable, MallocSizeOf)] pub(crate) struct NodeFlags(u16); bitflags! { impl NodeFlags: u16 { /// Specifies whether this node is in a document. /// /// const IS_IN_A_DOCUMENT_TREE = 1 << 0; /// Specifies whether this node needs style recalc on next reflow. const HAS_DIRTY_DESCENDANTS = 1 << 1; /// Specifies whether or not there is an authentic click in progress on /// this element. const CLICK_IN_PROGRESS = 1 << 2; /// Specifies whether this node is focusable and whether it is supposed /// to be reachable with using sequential focus navigation."] const SEQUENTIALLY_FOCUSABLE = 1 << 3; // There are two free bits here. /// 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; /// Whether this node participates in a shadow tree. const IS_IN_SHADOW_TREE = 1 << 9; /// Specifies whether this node's shadow-including root is a document. /// /// const IS_CONNECTED = 1 << 10; /// Whether this node has a weird parser insertion mode. i.e whether setting innerHTML /// needs extra work or not const HAS_WEIRD_PARSER_INSERTION_MODE = 1 << 11; } } /// suppress observers flag /// /// #[derive(Clone, Copy, MallocSizeOf)] enum SuppressObserver { Suppressed, Unsuppressed, } 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>, can_gc: CanGc) { 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(before) => { assert!(before.parent_node.get().as_deref() == Some(self)); let prev_sibling = before.GetPreviousSibling(); match prev_sibling { None => { assert!(self.first_child.get().as_deref() == Some(before)); 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_is_in_a_document_tree = self.is_in_a_document_tree(); let parent_in_shadow_tree = self.is_in_a_shadow_tree(); let parent_is_connected = self.is_connected(); for node in new_child.traverse_preorder(ShadowIncluding::No) { if parent_in_shadow_tree { if let Some(shadow_root) = self.containing_shadow_root() { node.set_containing_shadow_root(Some(&*shadow_root)); } debug_assert!(node.containing_shadow_root().is_some()); } node.set_flag( NodeFlags::IS_IN_A_DOCUMENT_TREE, parent_is_in_a_document_tree, ); node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, parent_in_shadow_tree); node.set_flag(NodeFlags::IS_CONNECTED, parent_is_connected); // Out-of-document elements never have the descendants flag set. debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS)); vtable_for(&node).bind_to_tree( &BindContext { tree_connected: parent_is_connected, tree_is_in_a_document_tree: parent_is_in_a_document_tree, tree_is_in_a_shadow_tree: parent_in_shadow_tree, }, can_gc, ); } } /// Implements the "unsafely set HTML" algorithm as specified in: /// pub fn unsafely_set_html( target: &Node, context_element: &Element, html: DOMString, can_gc: CanGc, ) { // Step 1. Let newChildren be the result of the HTML fragment parsing algorithm. let new_children = ServoParser::parse_html_fragment(context_element, html, true, can_gc); // Step 2. Let fragment be a new DocumentFragment whose node document is contextElement's node document. let context_document = context_element.owner_document(); let fragment = DocumentFragment::new(&context_document, can_gc); // Step 3. For each node in newChildren, append node to fragment. for child in new_children { fragment .upcast::() .AppendChild(&child, can_gc) .unwrap(); } // Step 4. Replace all with fragment within target. Node::replace_all(Some(fragment.upcast()), target, can_gc); } pub(crate) fn clean_up_style_and_layout_data(&self) { self.owner_doc().cancel_animations_for_node(self); self.style_data.borrow_mut().take(); self.layout_data.borrow_mut().take(); } /// Clean up flags and runs steps 11-14 of remove a node. /// pub(crate) fn complete_remove_subtree(root: &Node, context: &UnbindContext, can_gc: CanGc) { // Flags that reset when a node is disconnected const RESET_FLAGS: NodeFlags = NodeFlags::IS_IN_A_DOCUMENT_TREE .union(NodeFlags::IS_CONNECTED) .union(NodeFlags::HAS_DIRTY_DESCENDANTS) .union(NodeFlags::HAS_SNAPSHOT) .union(NodeFlags::HANDLED_SNAPSHOT); for node in root.traverse_preorder(ShadowIncluding::No) { node.set_flag(RESET_FLAGS | NodeFlags::IS_IN_SHADOW_TREE, false); // If the element has a shadow root attached to it then we traverse that as well, // but without touching the IS_IN_SHADOW_TREE flags of the children if let Some(shadow_root) = node.downcast::().and_then(Element::shadow_root) { for node in shadow_root .upcast::() .traverse_preorder(ShadowIncluding::Yes) { node.set_flag(RESET_FLAGS, false); } } } // Step 12. let is_parent_connected = context.parent.is_connected(); for node in root.traverse_preorder(ShadowIncluding::Yes) { node.clean_up_style_and_layout_data(); // Step 11 & 14.1. Run the removing steps. // 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
. vtable_for(&node).unbind_from_tree(context, can_gc); // Step 12 & 14.2. Enqueue disconnected custom element reactions. if is_parent_connected { if let Some(element) = node.as_custom_element() { ScriptThread::enqueue_callback_reaction( &element, CallbackReaction::Disconnected, None, ); } } } } /// 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, can_gc: CanGc) { assert!(child.parent_node.get().as_deref() == Some(self)); self.note_dirty_descendants(); let prev_sibling = child.GetPreviousSibling(); match prev_sibling { None => { self.first_child.set(child.next_sibling.get().as_deref()); }, Some(ref prev_sibling) => { prev_sibling .next_sibling .set(child.next_sibling.get().as_deref()); }, } let next_sibling = child.GetNextSibling(); match next_sibling { None => { self.last_child.set(child.prev_sibling.get().as_deref()); }, Some(ref next_sibling) => { next_sibling .prev_sibling .set(child.prev_sibling.get().as_deref()); }, } let context = UnbindContext::new( self, prev_sibling.as_deref(), next_sibling.as_deref(), 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); Self::complete_remove_subtree(child, &context, can_gc); } pub(crate) fn to_untrusted_node_address(&self) -> UntrustedNodeAddress { UntrustedNodeAddress(self.reflector().get_jsobject().get() as *const c_void) } pub(crate) fn to_opaque(&self) -> OpaqueNode { OpaqueNode(self.reflector().get_jsobject().get() as usize) } pub(crate) fn as_custom_element(&self) -> Option> { self.downcast::().and_then(|element| { if element.is_custom() { assert!(element.get_custom_element_definition().is_some()); Some(DomRoot::from_ref(element)) } else { None } }) } /// pub(crate) fn fire_synthetic_pointer_event_not_trusted(&self, name: DOMString, can_gc: CanGc) { // Spec says the choice of which global to create the pointer event // on is not well-defined, // and refers to heycam/webidl#135 let window = self.owner_window(); // let pointer_event = PointerEvent::new( &window, // ambiguous in spec name, EventBubbles::Bubbles, // Step 3: bubbles EventCancelable::Cancelable, // Step 3: cancelable Some(&window), // Step 7: view 0, // detail uninitialized 0, // coordinates uninitialized 0, // coordinates uninitialized 0, // coordinates uninitialized 0, // coordinates uninitialized false, // ctrl_key false, // alt_key false, // shift_key false, // meta_key 0, // button, left mouse button 0, // buttons None, // related_target None, // point_in_target PointerId::NonPointerDevice as i32, // pointer_id 1, // width 1, // height 0.5, // pressure 0.0, // tangential_pressure 0, // tilt_x 0, // tilt_y 0, // twist PI / 2.0, // altitude_angle 0.0, // azimuth_angle DOMString::from(""), // pointer_type false, // is_primary vec![], // coalesced_events vec![], // predicted_events can_gc, ); // Step 4. Set event's composed flag. pointer_event.upcast::().set_composed(true); // Step 5. If the not trusted flag is set, initialize event's isTrusted attribute to false. pointer_event.upcast::().set_trusted(false); // Step 6,8. TODO keyboard modifiers pointer_event .upcast::() .dispatch(self.upcast::(), false, can_gc); } pub(crate) fn parent_directionality(&self) -> String { let mut current = self.GetParentNode(); loop { match current { Some(node) => { if let Some(directionality) = node .downcast::() .and_then(|html_element| html_element.directionality()) { return directionality; } else { current = node.GetParentNode(); } }, None => return "ltr".to_owned(), } } } } pub(crate) struct QuerySelectorIterator { selectors: SelectorList, iterator: TreeIterator, } impl QuerySelectorIterator { fn new(iter: TreeIterator, selectors: SelectorList) -> QuerySelectorIterator { QuerySelectorIterator { selectors, iterator: iter, } } } impl Iterator for QuerySelectorIterator { type Item = DomRoot; fn next(&mut self) -> Option> { 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. let mut nth_index_cache = Default::default(); let mut ctx = MatchingContext::new( MatchingMode::Normal, None, &mut nth_index_cache, node.owner_doc().quirks_mode(), NeedsSelectorFlags::No, MatchingForInvalidation::No, ); if let Some(element) = DomRoot::downcast(node) { if matches_selector_list( selectors, &SelectorWrapper::Borrowed(&element), &mut ctx, ) { return Some(DomRoot::upcast(element)); } } None }) .next() } } impl Node { fn rare_data(&self) -> Ref>> { self.rare_data.borrow() } fn ensure_rare_data(&self) -> RefMut> { let mut rare_data = self.rare_data.borrow_mut(); if rare_data.is_none() { *rare_data = Some(Default::default()); } RefMut::map(rare_data, |rare_data| rare_data.as_mut().unwrap()) } /// Returns true if this node is before `other` in the same connected DOM /// tree. pub(crate) 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. Lazily initialize the /// raredata if it does not exist. pub(crate) fn registered_mutation_observers_mut(&self) -> RefMut> { RefMut::map(self.ensure_rare_data(), |rare_data| { &mut rare_data.mutation_observers }) } pub(crate) fn registered_mutation_observers(&self) -> Option>> { let rare_data: Ref<_> = self.rare_data.borrow(); if rare_data.is_none() { return None; } Some(Ref::map(rare_data, |rare_data| { &rare_data.as_ref().unwrap().mutation_observers })) } /// Add a new mutation observer for a given node. pub(crate) fn add_mutation_observer(&self, observer: RegisteredObserver) { self.ensure_rare_data().mutation_observers.push(observer); } /// Removes the mutation observer for a given node. pub(crate) fn remove_mutation_observer(&self, observer: &MutationObserver) { self.ensure_rare_data() .mutation_observers .retain(|reg_obs| &*reg_obs.observer != observer) } /// Dumps the subtree rooted at this node, for debugging. pub(crate) fn dump(&self) { self.dump_indent(0); } /// Dumps the node tree, for debugging, with indentation. pub(crate) 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(crate) fn debug_str(&self) -> String { format!("{:?}", self.type_id()) } /// pub(crate) fn is_in_a_document_tree(&self) -> bool { self.flags.get().contains(NodeFlags::IS_IN_A_DOCUMENT_TREE) } /// Return true iff node's root is a shadow-root. pub(crate) fn is_in_a_shadow_tree(&self) -> bool { self.flags.get().contains(NodeFlags::IS_IN_SHADOW_TREE) } pub(crate) fn has_weird_parser_insertion_mode(&self) -> bool { self.flags .get() .contains(NodeFlags::HAS_WEIRD_PARSER_INSERTION_MODE) } pub(crate) fn set_weird_parser_insertion_mode(&self) { self.set_flag(NodeFlags::HAS_WEIRD_PARSER_INSERTION_MODE, true) } /// pub(crate) fn is_connected(&self) -> bool { self.flags.get().contains(NodeFlags::IS_CONNECTED) } /// Returns the type ID of this node. pub(crate) fn type_id(&self) -> NodeTypeId { match *self.eventtarget.type_id() { EventTargetTypeId::Node(type_id) => type_id, _ => unreachable!(), } } /// pub(crate) fn len(&self) -> u32 { match self.type_id() { NodeTypeId::DocumentType => 0, NodeTypeId::CharacterData(_) => self.downcast::().unwrap().Length(), _ => self.children_count(), } } pub(crate) fn is_empty(&self) -> bool { // A node is considered empty if its length is 0. self.len() == 0 } /// pub(crate) fn index(&self) -> u32 { self.preceding_siblings().count() as u32 } /// Returns true if this node has a parent. pub(crate) fn has_parent(&self) -> bool { self.parent_node.get().is_some() } pub(crate) fn children_count(&self) -> u32 { self.children_count.get() } pub(crate) fn ranges(&self) -> RefMut { RefMut::map(self.ensure_rare_data(), |rare_data| &mut rare_data.ranges) } pub(crate) fn ranges_is_empty(&self) -> bool { match self.rare_data().as_ref() { Some(data) => data.ranges.is_empty(), None => false, } } #[inline] pub(crate) fn is_doctype(&self) -> bool { self.type_id() == NodeTypeId::DocumentType } pub(crate) fn get_flag(&self, flag: NodeFlags) -> bool { self.flags.get().contains(flag) } pub(crate) 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); } // FIXME(emilio): This and the function below should move to Element. pub(crate) fn note_dirty_descendants(&self) { self.owner_doc().note_node_with_dirty_descendants(self); } pub(crate) fn has_dirty_descendants(&self) -> bool { self.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) } pub(crate) 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 = DomRoot::upcast(self.owner_doc()); let version = cmp::max( self.inclusive_descendants_version(), doc.inclusive_descendants_version(), ) + 1; for ancestor in self.inclusive_ancestors(ShadowIncluding::No) { ancestor.inclusive_descendants_version.set(version); } doc.inclusive_descendants_version.set(version); } pub(crate) fn dirty(&self, damage: NodeDamage) { self.rev_version(); if !self.is_connected() { return; } match self.type_id() { NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => { self.parent_node.get().unwrap().dirty(damage) }, NodeTypeId::Element(_) => self.downcast::().unwrap().restyle(damage), NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot) => self .downcast::() .unwrap() .Host() .upcast::() .restyle(damage), _ => {}, }; } /// The maximum version number of this node's descendants, including itself pub(crate) fn inclusive_descendants_version(&self) -> u64 { self.inclusive_descendants_version.get() } /// Iterates over this node and all its descendants, in preorder. pub(crate) fn traverse_preorder(&self, shadow_including: ShadowIncluding) -> TreeIterator { TreeIterator::new(self, shadow_including) } pub(crate) fn inclusively_following_siblings( &self, ) -> impl Iterator> + use<> { SimpleNodeIterator { current: Some(DomRoot::from_ref(self)), next_node: |n| n.GetNextSibling(), } } pub(crate) fn inclusively_preceding_siblings( &self, ) -> impl Iterator> + use<> { SimpleNodeIterator { current: Some(DomRoot::from_ref(self)), next_node: |n| n.GetPreviousSibling(), } } pub(crate) fn common_ancestor( &self, other: &Node, shadow_including: ShadowIncluding, ) -> Option> { self.inclusive_ancestors(shadow_including).find(|ancestor| { other .inclusive_ancestors(shadow_including) .any(|node| node == *ancestor) }) } pub(crate) fn common_ancestor_in_flat_tree(&self, other: &Node) -> Option> { self.inclusive_ancestors_in_flat_tree().find(|ancestor| { other .inclusive_ancestors_in_flat_tree() .any(|node| node == *ancestor) }) } pub(crate) fn is_inclusive_ancestor_of(&self, parent: &Node) -> bool { self == parent || self.is_ancestor_of(parent) } pub(crate) fn is_ancestor_of(&self, parent: &Node) -> bool { parent.ancestors().any(|ancestor| &*ancestor == self) } pub(crate) fn is_shadow_including_inclusive_ancestor_of(&self, node: &Node) -> bool { node.inclusive_ancestors(ShadowIncluding::Yes) .any(|ancestor| &*ancestor == self) } pub(crate) fn following_siblings(&self) -> impl Iterator> + use<> { SimpleNodeIterator { current: self.GetNextSibling(), next_node: |n| n.GetNextSibling(), } } pub(crate) fn preceding_siblings(&self) -> impl Iterator> + use<> { SimpleNodeIterator { current: self.GetPreviousSibling(), next_node: |n| n.GetPreviousSibling(), } } pub(crate) fn following_nodes(&self, root: &Node) -> FollowingNodeIterator { FollowingNodeIterator { current: Some(DomRoot::from_ref(self)), root: DomRoot::from_ref(root), } } pub(crate) fn preceding_nodes(&self, root: &Node) -> PrecedingNodeIterator { PrecedingNodeIterator { current: Some(DomRoot::from_ref(self)), root: DomRoot::from_ref(root), } } pub(crate) fn descending_last_children(&self) -> impl Iterator> + use<> { SimpleNodeIterator { current: self.GetLastChild(), next_node: |n| n.GetLastChild(), } } pub(crate) fn is_parent_of(&self, child: &Node) -> bool { child .parent_node .get() .is_some_and(|parent| &*parent == self) } pub(crate) 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(crate) fn bounding_content_box(&self, can_gc: CanGc) -> Option> { self.owner_window().content_box_query(self, can_gc) } pub(crate) fn bounding_content_box_or_zero(&self, can_gc: CanGc) -> Rect { self.bounding_content_box(can_gc).unwrap_or_else(Rect::zero) } pub(crate) fn bounding_content_box_no_reflow(&self) -> Option> { self.owner_window().content_box_query_unchecked(self) } pub(crate) fn content_boxes(&self, can_gc: CanGc) -> Vec> { self.owner_window().content_boxes_query(self, can_gc) } pub(crate) fn client_rect(&self, can_gc: CanGc) -> Rect { self.owner_window().client_rect_query(self, can_gc) } /// /// pub(crate) fn scroll_area(&self, can_gc: CanGc) -> Rect { // "1. Let document be the element’s node document."" let document = self.owner_doc(); // "2. If document is not the active document, return zero and terminate these steps."" if !document.is_active() { return Rect::zero(); } // "3. Let viewport width/height be the width of the viewport excluding the width/height of the // scroll bar, if any, or zero if there is no viewport." let window = document.window(); let viewport = Size2D::new(window.InnerWidth(), window.InnerHeight()); let in_quirks_mode = document.quirks_mode() == QuirksMode::Quirks; let is_root = self.downcast::().is_some_and(|e| e.is_root()); let is_body_element = self .downcast::() .is_some_and(|e| e.is_the_html_body_element()); // "4. If the element is the root element and document is not in quirks mode // return max(viewport scrolling area width/height, viewport width/height)." // "5. If the element is the body element, document is in quirks mode and the // element is not potentially scrollable, return max(viewport scrolling area // width, viewport width)." if (is_root && !in_quirks_mode) || (is_body_element && in_quirks_mode) { let viewport_scrolling_area = window.scrolling_area_query(None, can_gc); return Rect::new( viewport_scrolling_area.origin, viewport_scrolling_area.size.max(viewport), ); } // "6. If the element does not have any associated box return zero and terminate // these steps." // "7. Return the width of the element’s scrolling area." window.scrolling_area_query(Some(self), can_gc) } pub(crate) fn scroll_offset(&self) -> Vector2D { let document = self.owner_doc(); let window = document.window(); window.scroll_offset_query(self).to_untyped() } /// pub(crate) fn before(&self, nodes: Vec, can_gc: CanGc) -> 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, can_gc)?; // 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.as_deref(), can_gc)?; Ok(()) } /// pub(crate) fn after(&self, nodes: Vec, can_gc: CanGc) -> 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, can_gc)?; // Step 5. Node::pre_insert(&node, &parent, viable_next_sibling.as_deref(), can_gc)?; Ok(()) } /// pub(crate) fn replace_with(&self, nodes: Vec, can_gc: CanGc) -> ErrorResult { // Step 1. Let parent be this’s parent. let Some(parent) = self.GetParentNode() else { // Step 2. If parent is null, then return. return Ok(()); }; // Step 3. Let viableNextSibling be this’s first following sibling not in nodes; otherwise null. let viable_next_sibling = first_node_not_in(self.following_siblings(), &nodes); // Step 4. Let node be the result of converting nodes into a node, given nodes and this’s node document. let node = self .owner_doc() .node_from_nodes_and_strings(nodes, can_gc)?; if self.parent_node == Some(&*parent) { // Step 5. If this’s parent is parent, replace this with node within parent. parent.ReplaceChild(&node, self, can_gc)?; } else { // Step 6. Otherwise, pre-insert node into parent before viableNextSibling. Node::pre_insert(&node, &parent, viable_next_sibling.as_deref(), can_gc)?; } Ok(()) } /// pub(crate) fn prepend(&self, nodes: Vec, can_gc: CanGc) -> ErrorResult { // Step 1. let doc = self.owner_doc(); let node = doc.node_from_nodes_and_strings(nodes, can_gc)?; // Step 2. let first_child = self.first_child.get(); Node::pre_insert(&node, self, first_child.as_deref(), can_gc).map(|_| ()) } /// pub(crate) fn append(&self, nodes: Vec, can_gc: CanGc) -> ErrorResult { // Step 1. let doc = self.owner_doc(); let node = doc.node_from_nodes_and_strings(nodes, can_gc)?; // Step 2. self.AppendChild(&node, can_gc).map(|_| ()) } /// pub(crate) fn replace_children(&self, nodes: Vec, can_gc: CanGc) -> ErrorResult { // Step 1. let doc = self.owner_doc(); let node = doc.node_from_nodes_and_strings(nodes, can_gc)?; // Step 2. Node::ensure_pre_insertion_validity(&node, self, None)?; // Step 3. Node::replace_all(Some(&node), self, can_gc); Ok(()) } /// pub(crate) fn query_selector( &self, selectors: DOMString, ) -> Fallible>> { // Step 1. let doc = self.owner_doc(); match SelectorParser::parse_author_origin_no_namespace( &selectors, &UrlExtraData(doc.url().get_arc()), ) { // Step 2. Err(_) => Err(Error::Syntax), // Step 3. Ok(selectors) => { let mut nth_index_cache = Default::default(); let mut ctx = MatchingContext::new( MatchingMode::Normal, None, &mut nth_index_cache, doc.quirks_mode(), NeedsSelectorFlags::No, MatchingForInvalidation::No, ); let mut descendants = self.traverse_preorder(ShadowIncluding::No); // Skip the root of the tree. assert!(&*descendants.next().unwrap() == self); Ok(descendants.filter_map(DomRoot::downcast).find(|element| { matches_selector_list(&selectors, &SelectorWrapper::Borrowed(element), &mut ctx) })) }, } } /// /// 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(crate) fn query_selector_iter( &self, selectors: DOMString, ) -> Fallible { // Step 1. let url = self.owner_doc().url(); match SelectorParser::parse_author_origin_no_namespace( &selectors, &UrlExtraData(url.get_arc()), ) { // Step 2. Err(_) => Err(Error::Syntax), // Step 3. Ok(selectors) => { let mut descendants = self.traverse_preorder(ShadowIncluding::No); // Skip the root of the tree. assert!(&*descendants.next().unwrap() == self); Ok(QuerySelectorIterator::new(descendants, selectors)) }, } } /// #[allow(unsafe_code)] pub(crate) fn query_selector_all(&self, selectors: DOMString) -> Fallible> { let window = self.owner_window(); let iter = self.query_selector_iter(selectors)?; Ok(NodeList::new_simple_list(&window, iter, CanGc::note())) } pub(crate) fn ancestors(&self) -> impl Iterator> + use<> { SimpleNodeIterator { current: self.GetParentNode(), next_node: |n| n.GetParentNode(), } } /// pub(crate) fn inclusive_ancestors( &self, shadow_including: ShadowIncluding, ) -> impl Iterator> + use<> { SimpleNodeIterator { current: Some(DomRoot::from_ref(self)), next_node: move |n| { if shadow_including == ShadowIncluding::Yes { if let Some(shadow_root) = n.downcast::() { return Some(DomRoot::from_ref(shadow_root.Host().upcast::())); } } n.GetParentNode() }, } } pub(crate) fn owner_doc(&self) -> DomRoot { self.owner_doc.get().unwrap() } pub(crate) fn set_owner_doc(&self, document: &Document) { self.owner_doc.set(Some(document)); } pub(crate) fn containing_shadow_root(&self) -> Option> { self.rare_data() .as_ref()? .containing_shadow_root .as_ref() .map(|sr| DomRoot::from_ref(&**sr)) } pub(crate) fn set_containing_shadow_root(&self, shadow_root: Option<&ShadowRoot>) { self.ensure_rare_data().containing_shadow_root = shadow_root.map(Dom::from_ref); } pub(crate) fn is_in_html_doc(&self) -> bool { self.owner_doc().is_html_document() } pub(crate) fn is_connected_with_browsing_context(&self) -> bool { self.is_connected() && self.owner_doc().browsing_context().is_some() } pub(crate) fn children(&self) -> impl Iterator> + use<> { SimpleNodeIterator { current: self.GetFirstChild(), next_node: |n| n.GetNextSibling(), } } pub(crate) fn rev_children(&self) -> impl Iterator> + use<> { SimpleNodeIterator { current: self.GetLastChild(), next_node: |n| n.GetPreviousSibling(), } } pub(crate) fn child_elements(&self) -> impl Iterator> + use<> { self.children() .filter_map(DomRoot::downcast as fn(_) -> _) .peekable() } pub(crate) fn remove_self(&self, can_gc: CanGc) { if let Some(ref parent) = self.GetParentNode() { Node::remove(self, parent, SuppressObserver::Unsuppressed, can_gc); } } pub(crate) fn unique_id(&self) -> String { let mut rare_data = self.ensure_rare_data(); if rare_data.unique_id.is_none() { let id = UniqueId::new(); ScriptThread::save_node_id(id.borrow().simple().to_string()); rare_data.unique_id = Some(id); } rare_data .unique_id .as_ref() .unwrap() .borrow() .simple() .to_string() } pub(crate) fn summarize(&self, can_gc: CanGc) -> NodeInfo { let USVString(base_uri) = self.BaseURI(); let node_type = self.NodeType(); let maybe_shadow_root = self.downcast::(); let shadow_root_mode = maybe_shadow_root .map(ShadowRoot::Mode) .map(ShadowRootMode::convert); let host = maybe_shadow_root .map(ShadowRoot::Host) .map(|host| host.upcast::().unique_id()); let is_shadow_host = self.downcast::().is_some_and(|potential_host| { let Some(root) = potential_host.shadow_root() else { return false; }; !root.is_user_agent_widget() || pref!(inspector_show_servo_internal_shadow_roots) }); let num_children = if is_shadow_host { // Shadow roots count as children self.ChildNodes(can_gc).Length() as usize + 1 } else { self.ChildNodes(can_gc).Length() as usize }; let window = self.owner_window(); let display = self .downcast::() .map(|elem| window.GetComputedStyle(elem, None)) .map(|style| style.Display().into()); NodeInfo { unique_id: self.unique_id(), host, base_uri, parent: self .GetParentNode() .map_or("".to_owned(), |node| node.unique_id()), node_type, is_top_level_document: node_type == NodeConstants::DOCUMENT_NODE, node_name: String::from(self.NodeName()), node_value: self.GetNodeValue().map(|v| v.into()), num_children, attrs: self.downcast().map(Element::summarize).unwrap_or(vec![]), is_shadow_host, shadow_root_mode, display, doctype_name: self .downcast::() .map(DocumentType::name) .cloned() .map(String::from), doctype_public_identifier: self .downcast::() .map(DocumentType::public_id) .cloned() .map(String::from), doctype_system_identifier: self .downcast::() .map(DocumentType::system_id) .cloned() .map(String::from), } } /// Used by `HTMLTableSectionElement::InsertRow` and `HTMLTableRowElement::InsertCell` pub(crate) fn insert_cell_or_row( &self, index: i32, get_items: F, new_child: G, can_gc: CanGc, ) -> Fallible> where F: Fn() -> DomRoot, G: Fn() -> DomRoot, I: DerivedFrom + DerivedFrom + DomObject, { if index < -1 { return Err(Error::IndexSize); } let tr = new_child(); { let tr_node = tr.upcast::(); if index == -1 { self.InsertBefore(tr_node, None, can_gc)?; } else { let items = get_items(); let node = match items .elements_iter() .map(DomRoot::upcast::) .map(Some) .chain(iter::once(None)) .nth(index as usize) { None => return Err(Error::IndexSize), Some(node) => node, }; self.InsertBefore(tr_node, node.as_deref(), can_gc)?; } } Ok(DomRoot::upcast::(tr)) } /// Used by `HTMLTableSectionElement::DeleteRow` and `HTMLTableRowElement::DeleteCell` pub(crate) fn delete_cell_or_row( &self, index: i32, get_items: F, is_delete_type: G, can_gc: CanGc, ) -> ErrorResult where F: Fn() -> DomRoot, G: Fn(&Element) -> bool, { let element = match index { index if index < -1 => return Err(Error::IndexSize), -1 => { let last_child = self.upcast::().GetLastChild(); match last_child.and_then(|node| { node.inclusively_preceding_siblings() .filter_map(DomRoot::downcast::) .find(|elem| is_delete_type(elem)) }) { Some(element) => element, None => return Ok(()), } }, index => match get_items().Item(index as u32) { Some(element) => element, None => return Err(Error::IndexSize), }, }; element.upcast::().remove_self(can_gc); Ok(()) } pub(crate) fn get_stylesheet(&self) -> Option> { if let Some(node) = self.downcast::() { node.get_stylesheet() } else if let Some(node) = self.downcast::() { node.get_stylesheet() } else { None } } pub(crate) fn get_cssom_stylesheet(&self) -> Option> { if let Some(node) = self.downcast::() { node.get_cssom_stylesheet() } else if let Some(node) = self.downcast::() { node.get_cssom_stylesheet(CanGc::note()) } else { None } } pub(crate) fn is_styled(&self) -> bool { self.style_data.borrow().is_some() } pub(crate) fn is_display_none(&self) -> bool { self.style_data.borrow().as_ref().is_none_or(|data| { data.element_data .borrow() .styles .primary() .get_box() .display .is_none() }) } pub(crate) fn style(&self, can_gc: CanGc) -> Option> { if !self .owner_window() .layout_reflow(QueryMsg::StyleQuery, can_gc) { return None; } self.style_data .borrow() .as_ref() .map(|data| data.element_data.borrow().styles.primary().clone()) } /// pub(crate) fn assign_slottables_for_a_tree(&self) { // NOTE: This method traverses all descendants of the node and is potentially very // expensive. If the node is not a shadow root then assigning slottables to it won't // have any effect, so we take a fast path out. let Some(shadow_root) = self.downcast::() else { return; }; if !shadow_root.has_slot_descendants() { return; } // > To assign slottables for a tree, given a node root, run assign slottables for each slot // > slot in root’s inclusive descendants, in tree order. for node in self.traverse_preorder(ShadowIncluding::No) { if let Some(slot) = node.downcast::() { slot.assign_slottables(); } } } pub(crate) fn assigned_slot(&self) -> Option> { let assigned_slot = self .rare_data .borrow() .as_ref()? .slottable_data .assigned_slot .as_ref()? .as_rooted(); Some(assigned_slot) } pub(crate) fn set_assigned_slot(&self, assigned_slot: Option<&HTMLSlotElement>) { self.ensure_rare_data().slottable_data.assigned_slot = assigned_slot.map(Dom::from_ref); } pub(crate) fn manual_slot_assignment(&self) -> Option> { let manually_assigned_slot = self .rare_data .borrow() .as_ref()? .slottable_data .manual_slot_assignment .as_ref()? .as_rooted(); Some(manually_assigned_slot) } pub(crate) fn set_manual_slot_assignment( &self, manually_assigned_slot: Option<&HTMLSlotElement>, ) { self.ensure_rare_data() .slottable_data .manual_slot_assignment = manually_assigned_slot.map(Dom::from_ref); } /// Gets the parent of this node from the perspective of layout and style. /// /// The returned node is the node's assigned slot, if any, or the /// shadow host if it's a shadow root. Otherwise, it is the node's /// parent. pub(crate) fn parent_in_flat_tree(&self) -> Option> { if let Some(assigned_slot) = self.assigned_slot() { return Some(DomRoot::upcast(assigned_slot)); } let parent_or_none = self.GetParentNode(); if let Some(parent) = parent_or_none.as_deref() { if let Some(shadow_root) = parent.downcast::() { return Some(DomRoot::from_ref(shadow_root.Host().upcast::())); } } parent_or_none } pub(crate) fn inclusive_ancestors_in_flat_tree( &self, ) -> impl Iterator> + use<> { SimpleNodeIterator { current: Some(DomRoot::from_ref(self)), next_node: move |n| n.parent_in_flat_tree(), } } } /// Iterate through `nodes` until we find a `Node` that is not in `not_in` fn first_node_not_in(mut nodes: I, not_in: &[NodeOrString]) -> Option> where I: Iterator>, { 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(crate) unsafe fn from_untrusted_node_address(candidate: UntrustedNodeAddress) -> DomRoot { DomRoot::from_ref(Node::from_untrusted_node_address(candidate)) } #[allow(unsafe_code)] pub(crate) trait LayoutNodeHelpers<'dom> { fn type_id_for_layout(self) -> NodeTypeId; fn composed_parent_node_ref(self) -> Option>; fn first_child_ref(self) -> Option>; fn last_child_ref(self) -> Option>; fn prev_sibling_ref(self) -> Option>; fn next_sibling_ref(self) -> Option>; fn owner_doc_for_layout(self) -> LayoutDom<'dom, Document>; fn containing_shadow_root_for_layout(self) -> Option>; fn assigned_slot_for_layout(self) -> Option>; fn is_element_for_layout(&self) -> bool; unsafe fn get_flag(self, flag: NodeFlags) -> bool; unsafe fn set_flag(self, flag: NodeFlags, value: bool); fn style_data(self) -> Option<&'dom StyleData>; fn layout_data(self) -> Option<&'dom GenericLayoutData>; /// Initialize the style data of this node. /// /// # Safety /// /// This method is unsafe because it modifies the given node during /// layout. Callers should ensure that no other layout thread is /// attempting to read or modify the opaque layout data of this node. unsafe fn initialize_style_data(self); /// Initialize the opaque layout data of this node. /// /// # Safety /// /// This method is unsafe because it modifies the given node during /// layout. Callers should ensure that no other layout thread is /// attempting to read or modify the opaque layout data of this node. unsafe fn initialize_layout_data(self, data: Box); /// Clear the style and opaque layout data of this node. /// /// # Safety /// /// This method is unsafe because it modifies the given node during /// layout. Callers should ensure that no other layout thread is /// attempting to read or modify the opaque layout data of this node. unsafe fn clear_style_and_layout_data(self); /// Whether this element is a `` rendered as text or a `