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