/* 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::cmp::Ordering; use std::default::Default; use std::f64::consts::PI; use std::marker::PhantomData; use std::ops::Deref; use std::slice::from_ref; use std::{cmp, fmt, iter}; use app_units::Au; use bitflags::bitflags; use devtools_traits::NodeInfo; use dom_struct::dom_struct; use embedder_traits::UntrustedNodeAddress; use euclid::default::Size2D; use euclid::{Point2D, Rect}; use html5ever::serialize::HtmlSerializer; use html5ever::{Namespace, Prefix, QualName, ns, serialize as html_serialize}; use js::context::{JSContext, NoGC}; use js::jsapi::JSObject; use js::rust::HandleObject; use keyboard_types::Modifiers; use layout_api::{ AxesOverflow, BoxAreaType, CSSPixelRectIterator, GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType, PhysicalSides, SVGElementData, SharedSelection, TrustedNodeAddress, with_layout_state, }; use libc::{self, c_void, uintptr_t}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use net_traits::image_cache::Image; use pixels::ImageMetadata; use script_bindings::codegen::GenericBindings::EventBinding::EventMethods; use script_bindings::codegen::InheritTypes::DocumentFragmentTypeId; use script_traits::DocumentActivity; use servo_arc::Arc as ServoArc; use servo_base::id::{BrowsingContextId, PipelineId}; use servo_config::pref; use servo_url::ServoUrl; use smallvec::SmallVec; use style::Atom; use style::attr::AttrValue; use style::context::QuirksMode; use style::dom::OpaqueNode; use style::dom_apis::{QueryAll, QueryFirst}; use style::selector_parser::PseudoElement; use style::stylesheets::Stylesheet; use style_traits::CSSPixel; use uuid::Uuid; use xml5ever::{local_name, 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::HTMLElementBinding::HTMLElementMethods; 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::domname::namespace_from_domstring; 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::reflector::{ DomObject, DomObjectWrap, reflect_dom_object_with_proto_and_cx, }; use crate::dom::bindings::root::{ Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom, ToLayout, UnrootedDom, }; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::characterdata::CharacterData; use crate::dom::css::cssstylesheet::CSSStyleSheet; use crate::dom::css::stylesheetlist::StyleSheetListOwner; use crate::dom::customelementregistry::{ CallbackReaction, CustomElementRegistry, 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::{ AttributeMutationReason, CustomElementCreationMode, Element, ElementCreator, }; use crate::dom::event::{Event, EventBubbles, EventCancelable, EventFlags}; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::html::htmlcanvaselement::HTMLCanvasElement; use crate::dom::html::htmlcollection::HTMLCollection; use crate::dom::html::htmlelement::HTMLElement; use crate::dom::html::htmliframeelement::HTMLIFrameElement; use crate::dom::html::htmlimageelement::HTMLImageElement; use crate::dom::html::htmllinkelement::HTMLLinkElement; use crate::dom::html::htmlslotelement::{HTMLSlotElement, Slottable}; use crate::dom::html::htmlstyleelement::HTMLStyleElement; use crate::dom::html::htmltextareaelement::HTMLTextAreaElement; use crate::dom::html::htmlvideoelement::HTMLVideoElement; use crate::dom::html::input_element::HTMLInputElement; use crate::dom::mutationobserver::{Mutation, MutationObserver, RegisteredObserver}; use crate::dom::node::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::html::HtmlSerialize; use crate::dom::servoparser::{ServoParser, serialize_html_fragment}; use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot}; use crate::dom::svg::svgsvgelement::SVGSVGElement; use crate::dom::text::Text; use crate::dom::types::{CDATASection, KeyboardEvent}; use crate::dom::virtualmethods::{VirtualMethods, vtable_for}; use crate::dom::window::Window; use crate::layout_dom::{ServoDangerousStyleElement, ServoDangerousStyleNode}; 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, /// 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; // There are three 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; /// Whether this node resides in UA shadow DOM. Element within UA Shadow DOM /// will have a different style computation behavior const IS_IN_UA_WIDGET = 1 << 12; /// Whether this node has a pseudo-element style which uses `attr()` in the `content` attribute. const USES_ATTR_IN_CONTENT_ATTRIBUTE = 1 << 13; } } /// suppress observers flag /// /// #[derive(Clone, Copy, MallocSizeOf)] enum SuppressObserver { Suppressed, Unsuppressed, } pub(crate) enum ForceSlottableNodeReconciliation { Force, Skip, } 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, cx: &mut JSContext, 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(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(); let parent_is_in_ua_widget = self.is_in_ua_widget(); let context = BindContext::new(self, IsShadowTree::No); 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); node.set_flag(NodeFlags::IS_IN_UA_WIDGET, parent_is_in_ua_widget); // 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(cx, &context); } } /// Implements the "unsafely set HTML" algorithm as specified in: /// pub(crate) fn unsafely_set_html( target: &Node, context_element: &Element, html: DOMString, cx: &mut JSContext, ) { // Step 1. Let newChildren be the result of the HTML fragment parsing algorithm. let new_children = ServoParser::parse_html_fragment(context_element, html, true, cx); // 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(cx, &context_document); // Step 3. For each node in newChildren, append node to fragment. for child in new_children { fragment.upcast::().AppendChild(cx, &child).unwrap(); } // Step 4. Replace all with fragment within target. Node::replace_all(cx, Some(fragment.upcast()), target); } /// Clear this [`Node`]'s layout data and also clear the layout data of all children. /// Note that this clears layout data from all non-flat tree descendants and flat tree /// descendants. pub(crate) fn remove_layout_boxes_from_subtree(&self, no_gc: &NoGC) { for node in self.traverse_preorder_non_rooting(no_gc, ShadowIncluding::Yes) { node.layout_data.borrow_mut().take(); } } fn clean_up_style_and_layout_data(&self) { self.layout_data.borrow_mut().take(); if let Some(element) = self.downcast::() { element.clean_up_style_data(); } } /// Clean up flags and runs steps 11-14 of remove a node. /// pub(crate) fn complete_remove_subtree( cx: &mut JSContext, root: &Node, context: &UnbindContext, ) { // 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_non_rooting(cx.no_gc(), 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_non_rooting(cx.no_gc(), ShadowIncluding::Yes) { node.set_flag(RESET_FLAGS, false); } } } // Step 12. let is_parent_connected = context.parent.is_connected(); let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack(); // Since both the initial traversal in light dom and the inner traversal // in shadow DOM share the same code, we define a closure to prevent omissions. let cleanup_node = |cx: &mut JSContext, node: &Node| { node.owner_doc().cancel_animations_for_node(node); 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, CanGc::from_cx(cx)); // Step 12 & 14.2. Enqueue disconnected custom element reactions. if is_parent_connected { if let Some(element) = node.as_custom_element() { custom_element_reaction_stack.enqueue_callback_reaction( &element, CallbackReaction::Disconnected, None, ); } } }; for node in root.traverse_preorder(ShadowIncluding::No) { cleanup_node(cx, &node); // Make sure that we don't accidentally initialize the rare data for this node // by setting it to None if node.containing_shadow_root().is_some() { // Reset the containing shadowRoot after we unbind the node, since some elements // require the containing shadowRoot for cleanup logic (e.g.