Skip to main content

script/layout_dom/
servo_layout_node.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![expect(unsafe_code)]
6#![deny(missing_docs)]
7
8use std::fmt;
9
10use atomic_refcell::AtomicRef;
11use layout_api::{
12    GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutDataTrait, LayoutElement, LayoutNode,
13    LayoutNodeType, PseudoElementChain, SVGElementData, SharedSelection, TrustedNodeAddress,
14};
15use net_traits::image_cache::Image;
16use pixels::ImageMetadata;
17use servo_arc::Arc;
18use servo_base::id::{BrowsingContextId, PipelineId};
19use servo_base::text::{RangeAny, Utf32CodeUnits};
20use servo_url::ServoUrl;
21use style;
22use style::context::SharedStyleContext;
23use style::dom::{LayoutIterator, NodeInfo};
24use style::properties::ComputedValues;
25use style::selector_parser::PseudoElement;
26
27use super::ServoLayoutElement;
28use crate::dom::bindings::root::LayoutDom;
29use crate::dom::element::Element;
30use crate::dom::layout_dom::NodeTypeIdWrapper;
31use crate::dom::node::{Node, NodeFlags};
32use crate::layout_dom::{
33    ServoDangerousStyleNode, ServoLayoutDomTypeBundle, ServoLayoutNodeChildrenIterator,
34};
35
36impl fmt::Debug for LayoutDom<'_, Node> {
37    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38        if let Some(element) = self.downcast::<Element>() {
39            element.fmt(f)
40        } else if self.is_text_node_for_layout() {
41            write!(f, "<text node> ({:#x})", self.opaque().0)
42        } else {
43            write!(f, "<non-text node> ({:#x})", self.opaque().0)
44        }
45    }
46}
47
48/// A wrapper around a `LayoutDom<Node>` which provides a safe interface that
49/// can be used during layout. This implements the `LayoutNode` trait.
50#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
51pub struct ServoLayoutNode<'dom> {
52    /// The wrapped private DOM node.
53    pub(super) node: LayoutDom<'dom, Node>,
54    /// The possibly nested [`PseudoElementChain`] for this node.
55    pub(super) pseudo_element_chain: PseudoElementChain,
56}
57
58/// Those are supposed to be sound, but they aren't because the entire system
59/// between script and layout so far has been designed to work around their
60/// absence. Switching the entire thing to the inert crate infra will help.
61unsafe impl Send for ServoLayoutNode<'_> {}
62unsafe impl Sync for ServoLayoutNode<'_> {}
63
64impl<'dom> ServoLayoutNode<'dom> {
65    /// Create a new [`ServoLayoutNode`] for this given [`TrustedNodeAddress`].
66    ///
67    /// # Safety
68    ///
69    /// The address pointed to by `address` should point to a valid node in memory.
70    pub unsafe fn new(address: &TrustedNodeAddress) -> Self {
71        unsafe { LayoutDom::from_trusted_node_address(*address) }.into()
72    }
73
74    /// Get the first child of this node.
75    ///
76    /// # Safety
77    ///
78    /// This node should never be exposed directly to the layout interface, as that may allow
79    /// mutating a node that is being laid out in another thread. Thus, this should *never* be
80    /// made public or exposed in the `LayoutNode` trait.
81    pub(super) unsafe fn dangerous_first_child(&self) -> Option<Self> {
82        self.node.first_child_ref().map(Into::into)
83    }
84
85    /// Get the next sibling of this node.
86    ///
87    /// # Safety
88    ///
89    /// This node should never be exposed directly to the layout interface, as that may allow
90    /// mutating a node that is being laid out in another thread. Thus, this should *never* be
91    /// made public or exposed in the `LayoutNode` trait.
92    pub(super) unsafe fn dangerous_next_sibling(&self) -> Option<Self> {
93        self.node.next_sibling_ref().map(Into::into)
94    }
95
96    /// Get the previous sibling of this node.
97    ///
98    /// # Safety
99    ///
100    /// This node should never be exposed directly to the layout interface, as that may allow
101    /// mutating a node that is being laid out in another thread. Thus, this should *never* be
102    /// made public or exposed in the `LayoutNode` trait.
103    pub(super) unsafe fn dangerous_previous_sibling(&self) -> Option<Self> {
104        self.node.prev_sibling_ref().map(Into::into)
105    }
106}
107
108impl<'dom> From<LayoutDom<'dom, Node>> for ServoLayoutNode<'dom> {
109    fn from(node: LayoutDom<'dom, Node>) -> Self {
110        Self {
111            node,
112            pseudo_element_chain: Default::default(),
113        }
114    }
115}
116
117impl<'dom> LayoutNode<'dom> for ServoLayoutNode<'dom> {
118    type ConcreteTypeBundle = ServoLayoutDomTypeBundle<'dom>;
119
120    fn with_pseudo(&self, pseudo_element_type: PseudoElement) -> Option<Self> {
121        Some(
122            self.as_element()?
123                .with_pseudo(pseudo_element_type)?
124                .as_node(),
125        )
126    }
127
128    unsafe fn dangerous_style_node(self) -> ServoDangerousStyleNode<'dom> {
129        self.node.into()
130    }
131
132    unsafe fn dangerous_dom_parent(self) -> Option<Self> {
133        self.node.parent_node_ref().map(Into::into)
134    }
135
136    unsafe fn dangerous_flat_tree_parent(self) -> Option<Self> {
137        self.node
138            .traversal_parent()
139            .map(|parent_element| parent_element.upcast().into())
140    }
141
142    fn is_connected(&self) -> bool {
143        unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) }
144    }
145
146    fn layout_data(&self) -> Option<&'dom GenericLayoutData> {
147        self.node.layout_data()
148    }
149
150    fn opaque(&self) -> style::dom::OpaqueNode {
151        self.node.opaque()
152    }
153
154    fn pseudo_element_chain(&self) -> PseudoElementChain {
155        self.pseudo_element_chain
156    }
157
158    fn type_id(&self) -> Option<LayoutNodeType> {
159        if self.pseudo_element_chain.is_empty() {
160            Some(NodeTypeIdWrapper(self.node.type_id_for_layout()).into())
161        } else {
162            None
163        }
164    }
165
166    fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
167        if let Some(element) = self.as_element() {
168            element.style(context)
169        } else {
170            // Text nodes are not styled during traversal,instead we simply
171            // return parent style here and do cascading during layout.
172            debug_assert!(self.is_text_node());
173            self.parent_style(context)
174        }
175    }
176
177    fn parent_style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
178        if let Some(chain) = self.pseudo_element_chain.without_innermost() {
179            let mut parent = *self;
180            parent.pseudo_element_chain = chain;
181            return parent.style(context);
182        }
183        unsafe { self.dangerous_flat_tree_parent() }
184            .unwrap()
185            .style(context)
186    }
187
188    fn selected_style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
189        let Some(element) = self.as_element() else {
190            // TODO(stshine): What should the selected style be for text?
191            debug_assert!(self.is_text_node());
192            return self.parent_style(context);
193        };
194
195        let style_data = &element.element_data().styles;
196        let get_selected_style = || {
197            // This is a workaround for handling the `::selection` pseudos where it would not
198            // propagate to the children and Shadow DOM elements. For this case, UA widget
199            // inner elements should follow the originating element in terms of selection.
200            if self.node.is_in_ua_widget() {
201                return Some(
202                    Self::from(
203                        self.node
204                            .containing_shadow_root_for_layout()?
205                            .get_host_for_layout()
206                            .upcast(),
207                    )
208                    .selected_style(context),
209                );
210            }
211            style_data.pseudos.get(&PseudoElement::Selection).cloned()
212        };
213
214        get_selected_style().unwrap_or_else(|| style_data.primary().clone())
215    }
216
217    fn initialize_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
218        if self.node.layout_data().is_none() {
219            unsafe {
220                self.node
221                    .initialize_layout_data(Box::<RequestedLayoutDataType>::default());
222            }
223        }
224    }
225
226    fn flat_tree_children(&self) -> impl Iterator<Item = Self> {
227        LayoutIterator(ServoLayoutNodeChildrenIterator::new_for_flat_tree(*self))
228    }
229
230    fn dom_children(&self) -> impl Iterator<Item = Self> {
231        LayoutIterator(ServoLayoutNodeChildrenIterator::new_for_dom_tree(*self))
232    }
233
234    fn as_element(&self) -> Option<ServoLayoutElement<'dom>> {
235        self.node.downcast().map(|element| ServoLayoutElement {
236            element,
237            pseudo_element_chain: self.pseudo_element_chain,
238        })
239    }
240
241    fn as_html_element(&self) -> Option<ServoLayoutElement<'dom>> {
242        self.as_element()
243            .filter(|element| element.is_html_element())
244    }
245
246    fn text_content(self) -> AtomicRef<'dom, str> {
247        self.node.text_content()
248    }
249
250    fn document_selection_in_text_node(&self) -> Option<RangeAny<Utf32CodeUnits>> {
251        // Pseudo-elements do not ever have document selection.
252        if !self.pseudo_element_chain.is_empty() {
253            return None;
254        }
255
256        self.node.document_selection_in_text_node()
257    }
258
259    fn selection(&self) -> Option<SharedSelection> {
260        self.node.selection()
261    }
262
263    fn image_url(&self) -> Option<ServoUrl> {
264        self.node.image_url()
265    }
266
267    fn image_density(&self) -> Option<f64> {
268        self.node.image_density()
269    }
270
271    fn showing_broken_image_icon(&self) -> bool {
272        self.node.showing_broken_image_icon()
273    }
274
275    fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
276        self.node.image_data()
277    }
278
279    fn canvas_data(&self) -> Option<HTMLCanvasData> {
280        self.node.canvas_data()
281    }
282
283    fn media_data(&self) -> Option<HTMLMediaData> {
284        self.node.media_data()
285    }
286
287    fn svg_data(&self) -> Option<SVGElementData<'dom>> {
288        self.node.svg_data()
289    }
290
291    fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId> {
292        self.node.iframe_browsing_context_id()
293    }
294
295    fn iframe_pipeline_id(&self) -> Option<PipelineId> {
296        self.node.iframe_pipeline_id()
297    }
298
299    fn table_span(&self) -> Option<u32> {
300        self.node
301            .downcast::<Element>()
302            .and_then(|element| element.get_span())
303    }
304
305    fn table_colspan(&self) -> Option<u32> {
306        self.node
307            .downcast::<Element>()
308            .and_then(|element| element.get_colspan())
309    }
310
311    fn table_rowspan(&self) -> Option<u32> {
312        self.node
313            .downcast::<Element>()
314            .and_then(|element| element.get_rowspan())
315    }
316
317    fn set_uses_content_attribute_with_attr(&self, uses_content_attribute_with_attr: bool) {
318        unsafe {
319            self.node.set_flag(
320                NodeFlags::USES_ATTR_IN_CONTENT_ATTRIBUTE,
321                uses_content_attribute_with_attr,
322            )
323        }
324    }
325
326    fn is_single_line_text_input(&self) -> bool {
327        self.pseudo_element_chain.is_empty() && self.node.is_text_container_of_single_line_input()
328    }
329
330    fn is_root_of_user_agent_widget(&self) -> bool {
331        self.node.is_root_of_user_agent_widget()
332    }
333}
334
335impl NodeInfo for ServoLayoutNode<'_> {
336    fn is_element(&self) -> bool {
337        self.node.is_element_for_layout()
338    }
339
340    fn is_text_node(&self) -> bool {
341        self.node.is_text_node_for_layout()
342    }
343}