/* 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/. */ #![allow(unsafe_code)] use std::borrow::Cow; use std::fmt; use std::iter::FusedIterator; use base::id::{BrowsingContextId, PipelineId}; use fonts_traits::ByteIndex; use layout_api::wrapper_traits::{ LayoutDataTrait, LayoutNode, PseudoElementChain, ThreadSafeLayoutElement, ThreadSafeLayoutNode, }; use layout_api::{ GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType, SVGElementData, StyleData, TrustedNodeAddress, }; use net_traits::image_cache::Image; use pixels::ImageMetadata; use range::Range; use selectors::Element as _; use servo_arc::Arc; use servo_url::ServoUrl; use style; use style::dom::{NodeInfo, TElement, TNode, TShadowRoot}; use style::properties::ComputedValues; use style::selector_parser::PseudoElement; use super::{ ServoLayoutDocument, ServoLayoutElement, ServoShadowRoot, ServoThreadSafeLayoutElement, }; use crate::dom::bindings::inheritance::NodeTypeId; use crate::dom::bindings::root::LayoutDom; use crate::dom::element::{Element, LayoutElementHelpers}; use crate::dom::node::{LayoutNodeHelpers, Node, NodeFlags, NodeTypeIdWrapper}; /// A wrapper around a `LayoutDom` which provides a safe interface that /// can be used during layout. This implements the `LayoutNode` trait as well as /// several style and selectors traits for use during layout. This version /// should only be used on a single thread. If you need to use nodes across /// threads use ServoThreadSafeLayoutNode. #[derive(Clone, Copy, PartialEq)] #[repr(transparent)] pub struct ServoLayoutNode<'dom> { /// The wrapped private DOM node. pub(super) node: LayoutDom<'dom, Node>, } /// Those are supposed to be sound, but they aren't because the entire system /// between script and layout so far has been designed to work around their /// absence. Switching the entire thing to the inert crate infra will help. /// /// FIXME(mrobinson): These are required because Layout 2020 sends non-threadsafe /// nodes to different threads. This should be adressed in a comprehensive way. unsafe impl Send for ServoLayoutNode<'_> {} unsafe impl Sync for ServoLayoutNode<'_> {} impl fmt::Debug for ServoLayoutNode<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(el) = self.as_element() { el.fmt(f) } else if self.is_text_node() { write!(f, " ({:#x})", self.opaque().0) } else { write!(f, " ({:#x})", self.opaque().0) } } } impl<'dom> ServoLayoutNode<'dom> { pub(super) fn from_layout_js(n: LayoutDom<'dom, Node>) -> Self { ServoLayoutNode { node: n } } /// Create a new [`ServoLayoutNode`] for this given [`TrustedNodeAddress`]. /// /// # Safety /// /// The address pointed to by `address` should point to a valid node in memory. pub unsafe fn new(address: &TrustedNodeAddress) -> Self { let node = unsafe { LayoutDom::from_trusted_node_address(*address) }; ServoLayoutNode::from_layout_js(node) } pub(super) fn script_type_id(&self) -> NodeTypeId { self.node.type_id_for_layout() } /// Returns the interior of this node as a `LayoutDom`. pub(crate) fn get_jsmanaged(self) -> LayoutDom<'dom, Node> { self.node } pub(crate) fn assigned_slot(self) -> Option> { self.node .assigned_slot_for_layout() .as_ref() .map(LayoutDom::upcast) .map(ServoLayoutElement::from_layout_js) } } impl style::dom::NodeInfo for ServoLayoutNode<'_> { fn is_element(&self) -> bool { self.node.is_element_for_layout() } fn is_text_node(&self) -> bool { self.node.is_text_node_for_layout() } } impl<'dom> style::dom::TNode for ServoLayoutNode<'dom> { type ConcreteDocument = ServoLayoutDocument<'dom>; type ConcreteElement = ServoLayoutElement<'dom>; type ConcreteShadowRoot = ServoShadowRoot<'dom>; fn parent_node(&self) -> Option { self.node.parent_node_ref().map(Self::from_layout_js) } fn first_child(&self) -> Option { self.node.first_child_ref().map(Self::from_layout_js) } fn last_child(&self) -> Option { self.node.last_child_ref().map(Self::from_layout_js) } fn prev_sibling(&self) -> Option { self.node.prev_sibling_ref().map(Self::from_layout_js) } fn next_sibling(&self) -> Option { self.node.next_sibling_ref().map(Self::from_layout_js) } fn owner_doc(&self) -> Self::ConcreteDocument { ServoLayoutDocument::from_layout_js(self.node.owner_doc_for_layout()) } fn traversal_parent(&self) -> Option> { if let Some(assigned_slot) = self.assigned_slot() { return Some(assigned_slot); } let parent = self.parent_node()?; if let Some(shadow) = parent.as_shadow_root() { return Some(shadow.host()); }; parent.as_element() } fn opaque(&self) -> style::dom::OpaqueNode { self.get_jsmanaged().opaque() } fn debug_id(self) -> usize { self.opaque().0 } fn as_element(&self) -> Option> { self.node.downcast().map(ServoLayoutElement::from_layout_js) } fn as_document(&self) -> Option> { self.node .downcast() .map(ServoLayoutDocument::from_layout_js) } fn as_shadow_root(&self) -> Option> { self.node.downcast().map(ServoShadowRoot::from_layout_js) } fn is_in_document(&self) -> bool { unsafe { self.node.get_flag(NodeFlags::IS_IN_A_DOCUMENT_TREE) } } } impl<'dom> LayoutNode<'dom> for ServoLayoutNode<'dom> { type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'dom>; fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode { ServoThreadSafeLayoutNode::new(*self) } fn type_id(&self) -> LayoutNodeType { NodeTypeIdWrapper(self.script_type_id()).into() } unsafe fn initialize_style_and_layout_data(&self) { let inner = self.get_jsmanaged(); if inner.style_data().is_none() { unsafe { inner.initialize_style_data() }; } if inner.layout_data().is_none() { unsafe { inner.initialize_layout_data(Box::::default()) }; } } fn is_connected(&self) -> bool { unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) } } fn style_data(&self) -> Option<&'dom StyleData> { self.get_jsmanaged().style_data() } fn layout_data(&self) -> Option<&'dom GenericLayoutData> { self.get_jsmanaged().layout_data() } } /// A wrapper around a `ServoLayoutNode` that can be used safely on different threads. /// It's very important that this never mutate anything except this wrapped node and /// never access any other node apart from its parent. #[derive(Clone, Copy, Debug, PartialEq)] pub struct ServoThreadSafeLayoutNode<'dom> { /// The wrapped [`ServoLayoutNode`]. pub(super) node: ServoLayoutNode<'dom>, /// The possibly nested [`PseudoElementChain`] for this node. pub(super) pseudo_element_chain: PseudoElementChain, } impl<'dom> ServoThreadSafeLayoutNode<'dom> { /// Creates a new `ServoThreadSafeLayoutNode` from the given `ServoLayoutNode`. pub fn new(node: ServoLayoutNode<'dom>) -> Self { ServoThreadSafeLayoutNode { node, pseudo_element_chain: Default::default(), } } /// Returns the interior of this node as a `LayoutDom`. This is highly unsafe for layout to /// call and as such is marked `unsafe`. unsafe fn get_jsmanaged(&self) -> LayoutDom<'dom, Node> { self.node.get_jsmanaged() } /// Get the first child of this node. Important: this is not safe for /// layout to call, so it should *never* be made public. unsafe fn dangerous_first_child(&self) -> Option { let js_managed = unsafe { self.get_jsmanaged() }; js_managed .first_child_ref() .map(ServoLayoutNode::from_layout_js) .map(Self::new) } /// Get the next sibling of this node. Important: this is not safe for /// layout to call, so it should *never* be made public. unsafe fn dangerous_next_sibling(&self) -> Option { let js_managed = unsafe { self.get_jsmanaged() }; js_managed .next_sibling_ref() .map(ServoLayoutNode::from_layout_js) .map(Self::new) } /// Whether this is a container for the text within a single-line text input. This /// is used to solve the special case of line height for a text entry widget. /// // TODO(stevennovaryo): Remove the addition of HTMLInputElement here once all of the // input element is implemented with UA shadow DOM. This is temporary // workaround for past version of input element where we are // rendering it as a bare html element. pub fn is_single_line_text_input(&self) -> bool { self.type_id() == Some(LayoutNodeType::Element(LayoutElementType::HTMLInputElement)) || (self.pseudo_element_chain.is_empty() && self.node.node.is_text_container_of_single_line_input()) } pub fn is_text_input(&self) -> bool { self.node.node.is_text_input() } pub fn selected_style(&self) -> Arc { let Some(element) = self.as_element() else { // TODO(stshine): What should the selected style be for text? debug_assert!(self.is_text_node()); return self.parent_style(); }; let style_data = &element.style_data().styles; let get_selected_style = || { // This is a workaround for handling the `::selection` pseudos where it would not // propagate to the children and Shadow DOM elements. For this case, UA widget // inner elements should follow the originating element in terms of selection. if self.node.node.is_in_ua_widget() { return Some(element.containing_shadow_host()?.as_node().selected_style()); } style_data.pseudos.get(&PseudoElement::Selection).cloned() }; get_selected_style().unwrap_or_else(|| style_data.primary().clone()) } } impl style::dom::NodeInfo for ServoThreadSafeLayoutNode<'_> { fn is_element(&self) -> bool { self.node.is_element() } fn is_text_node(&self) -> bool { self.node.is_text_node() } } impl<'dom> ThreadSafeLayoutNode<'dom> for ServoThreadSafeLayoutNode<'dom> { type ConcreteNode = ServoLayoutNode<'dom>; type ConcreteThreadSafeLayoutElement = ServoThreadSafeLayoutElement<'dom>; type ConcreteElement = ServoLayoutElement<'dom>; type ChildrenIterator = ServoThreadSafeLayoutNodeChildrenIterator<'dom>; fn opaque(&self) -> style::dom::OpaqueNode { unsafe { self.get_jsmanaged().opaque() } } fn pseudo_element_chain(&self) -> PseudoElementChain { self.pseudo_element_chain } fn type_id(&self) -> Option { if self.pseudo_element_chain.is_empty() { Some(self.node.type_id()) } else { None } } fn parent_style(&self) -> Arc { let parent_element = self.node.traversal_parent().unwrap(); let parent_data = parent_element.borrow_data().unwrap(); parent_data.styles.primary().clone() } fn initialize_layout_data(&self) { let inner = self.node.get_jsmanaged(); if inner.layout_data().is_none() { unsafe { inner.initialize_layout_data(Box::::default()); } } } fn debug_id(self) -> usize { self.node.debug_id() } fn children(&self) -> style::dom::LayoutIterator { style::dom::LayoutIterator(ServoThreadSafeLayoutNodeChildrenIterator::new(*self)) } fn as_element(&self) -> Option> { self.node .as_element() .map(|el| ServoThreadSafeLayoutElement { element: el, pseudo_element_chain: self.pseudo_element_chain, }) } fn as_html_element(&self) -> Option> { self.as_element() .filter(|element| element.element.is_html_element()) } fn style_data(&self) -> Option<&'dom StyleData> { self.node.style_data() } fn layout_data(&self) -> Option<&'dom GenericLayoutData> { self.node.layout_data() } fn unsafe_get(self) -> Self::ConcreteNode { self.node } fn node_text_content(self) -> Cow<'dom, str> { unsafe { self.get_jsmanaged().text_content() } } fn selection(&self) -> Option> { let this = unsafe { self.get_jsmanaged() }; this.selection().map(|range| { Range::new( ByteIndex(range.start as isize), ByteIndex(range.len() as isize), ) }) } fn image_url(&self) -> Option { let this = unsafe { self.get_jsmanaged() }; this.image_url() } fn image_density(&self) -> Option { let this = unsafe { self.get_jsmanaged() }; this.image_density() } fn image_data(&self) -> Option<(Option, Option)> { let this = unsafe { self.get_jsmanaged() }; this.image_data() } fn canvas_data(&self) -> Option { let this = unsafe { self.get_jsmanaged() }; this.canvas_data() } fn media_data(&self) -> Option { let this = unsafe { self.get_jsmanaged() }; this.media_data() } fn svg_data(&self) -> Option { let this = unsafe { self.get_jsmanaged() }; this.svg_data() } // Can return None if the iframe has no nested browsing context fn iframe_browsing_context_id(&self) -> Option { let this = unsafe { self.get_jsmanaged() }; this.iframe_browsing_context_id() } // Can return None if the iframe has no nested browsing context fn iframe_pipeline_id(&self) -> Option { let this = unsafe { self.get_jsmanaged() }; this.iframe_pipeline_id() } fn get_span(&self) -> Option { unsafe { self.get_jsmanaged() .downcast::() .unwrap() .get_span() } } fn get_colspan(&self) -> Option { unsafe { self.get_jsmanaged() .downcast::() .unwrap() .get_colspan() } } fn get_rowspan(&self) -> Option { unsafe { self.get_jsmanaged() .downcast::() .unwrap() .get_rowspan() } } fn with_pseudo_element_chain(&self, pseudo_element_chain: PseudoElementChain) -> Self { Self { node: self.node, pseudo_element_chain, } } } pub enum ServoThreadSafeLayoutNodeChildrenIterator<'dom> { /// Iterating over the children of a node Node(Option>), /// Iterating over the assigned nodes of a `HTMLSlotElement` Slottables(> as IntoIterator>::IntoIter), } impl<'dom> ServoThreadSafeLayoutNodeChildrenIterator<'dom> { #[allow(unsafe_code)] fn new( parent: ServoThreadSafeLayoutNode<'dom>, ) -> ServoThreadSafeLayoutNodeChildrenIterator<'dom> { if let Some(element) = parent.as_element() { if let Some(shadow) = element.shadow_root() { return Self::new(shadow.as_node().to_threadsafe()); }; let slotted_nodes = element.slotted_nodes(); if !slotted_nodes.is_empty() { #[allow(clippy::unnecessary_to_owned)] // Clippy is wrong. return Self::Slottables(slotted_nodes.to_owned().into_iter()); } } Self::Node(unsafe { parent.dangerous_first_child() }) } } impl<'dom> Iterator for ServoThreadSafeLayoutNodeChildrenIterator<'dom> { type Item = ServoThreadSafeLayoutNode<'dom>; fn next(&mut self) -> Option { match self { Self::Node(node) => { let next_sibling = unsafe { (*node)?.dangerous_next_sibling() }; std::mem::replace(node, next_sibling) }, Self::Slottables(slots) => slots.next().map(|node| node.to_threadsafe()), } } } impl FusedIterator for ServoThreadSafeLayoutNodeChildrenIterator<'_> {}