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