script/layout_dom/
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
7use std::borrow::Cow;
8use std::fmt;
9use std::iter::FusedIterator;
10
11use base::id::{BrowsingContextId, PipelineId};
12use layout_api::wrapper_traits::{
13    LayoutDataTrait, LayoutNode, PseudoElementChain, SharedSelection, ThreadSafeLayoutElement,
14    ThreadSafeLayoutNode,
15};
16use layout_api::{
17    GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType,
18    SVGElementData, StyleData, TrustedNodeAddress,
19};
20use net_traits::image_cache::Image;
21use pixels::ImageMetadata;
22use selectors::Element as _;
23use servo_arc::Arc;
24use servo_url::ServoUrl;
25use style;
26use style::dom::{NodeInfo, TElement, TNode, TShadowRoot};
27use style::properties::ComputedValues;
28use style::selector_parser::PseudoElement;
29
30use super::{
31    ServoLayoutDocument, ServoLayoutElement, ServoShadowRoot, ServoThreadSafeLayoutElement,
32};
33use crate::dom::bindings::inheritance::NodeTypeId;
34use crate::dom::bindings::root::LayoutDom;
35use crate::dom::element::{Element, LayoutElementHelpers};
36use crate::dom::node::{LayoutNodeHelpers, Node, NodeFlags, NodeTypeIdWrapper};
37
38/// A wrapper around a `LayoutDom<Node>` which provides a safe interface that
39/// can be used during layout. This implements the `LayoutNode` trait as well as
40/// several style and selectors traits for use during layout. This version
41/// should only be used on a single thread. If you need to use nodes across
42/// threads use ServoThreadSafeLayoutNode.
43#[derive(Clone, Copy, PartialEq)]
44#[repr(transparent)]
45pub struct ServoLayoutNode<'dom> {
46    /// The wrapped private DOM node.
47    pub(super) node: LayoutDom<'dom, Node>,
48}
49
50/// Those are supposed to be sound, but they aren't because the entire system
51/// between script and layout so far has been designed to work around their
52/// absence. Switching the entire thing to the inert crate infra will help.
53///
54/// FIXME(mrobinson): These are required because Layout 2020 sends non-threadsafe
55/// nodes to different threads. This should be adressed in a comprehensive way.
56unsafe impl Send for ServoLayoutNode<'_> {}
57unsafe impl Sync for ServoLayoutNode<'_> {}
58
59impl fmt::Debug for ServoLayoutNode<'_> {
60    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61        if let Some(el) = self.as_element() {
62            el.fmt(f)
63        } else if self.is_text_node() {
64            write!(f, "<text node> ({:#x})", self.opaque().0)
65        } else {
66            write!(f, "<non-text node> ({:#x})", self.opaque().0)
67        }
68    }
69}
70
71impl<'dom> ServoLayoutNode<'dom> {
72    pub(super) fn from_layout_js(n: LayoutDom<'dom, Node>) -> Self {
73        ServoLayoutNode { node: n }
74    }
75
76    /// Create a new [`ServoLayoutNode`] for this given [`TrustedNodeAddress`].
77    ///
78    /// # Safety
79    ///
80    /// The address pointed to by `address` should point to a valid node in memory.
81    pub unsafe fn new(address: &TrustedNodeAddress) -> Self {
82        let node = unsafe { LayoutDom::from_trusted_node_address(*address) };
83        ServoLayoutNode::from_layout_js(node)
84    }
85
86    pub(super) fn script_type_id(&self) -> NodeTypeId {
87        self.node.type_id_for_layout()
88    }
89
90    /// Returns the interior of this node as a `LayoutDom`.
91    pub(crate) fn get_jsmanaged(self) -> LayoutDom<'dom, Node> {
92        self.node
93    }
94
95    pub(crate) fn assigned_slot(self) -> Option<ServoLayoutElement<'dom>> {
96        self.node
97            .assigned_slot_for_layout()
98            .as_ref()
99            .map(LayoutDom::upcast)
100            .map(ServoLayoutElement::from_layout_js)
101    }
102}
103
104impl style::dom::NodeInfo for ServoLayoutNode<'_> {
105    fn is_element(&self) -> bool {
106        self.node.is_element_for_layout()
107    }
108
109    fn is_text_node(&self) -> bool {
110        self.node.is_text_node_for_layout()
111    }
112}
113
114impl<'dom> style::dom::TNode for ServoLayoutNode<'dom> {
115    type ConcreteDocument = ServoLayoutDocument<'dom>;
116    type ConcreteElement = ServoLayoutElement<'dom>;
117    type ConcreteShadowRoot = ServoShadowRoot<'dom>;
118
119    fn parent_node(&self) -> Option<Self> {
120        self.node.parent_node_ref().map(Self::from_layout_js)
121    }
122
123    fn first_child(&self) -> Option<Self> {
124        self.node.first_child_ref().map(Self::from_layout_js)
125    }
126
127    fn last_child(&self) -> Option<Self> {
128        self.node.last_child_ref().map(Self::from_layout_js)
129    }
130
131    fn prev_sibling(&self) -> Option<Self> {
132        self.node.prev_sibling_ref().map(Self::from_layout_js)
133    }
134
135    fn next_sibling(&self) -> Option<Self> {
136        self.node.next_sibling_ref().map(Self::from_layout_js)
137    }
138
139    fn owner_doc(&self) -> Self::ConcreteDocument {
140        ServoLayoutDocument::from_layout_js(self.node.owner_doc_for_layout())
141    }
142
143    fn traversal_parent(&self) -> Option<ServoLayoutElement<'dom>> {
144        if let Some(assigned_slot) = self.assigned_slot() {
145            return Some(assigned_slot);
146        }
147        let parent = self.parent_node()?;
148        if let Some(shadow) = parent.as_shadow_root() {
149            return Some(shadow.host());
150        };
151        parent.as_element()
152    }
153
154    fn opaque(&self) -> style::dom::OpaqueNode {
155        self.get_jsmanaged().opaque()
156    }
157
158    fn debug_id(self) -> usize {
159        self.opaque().0
160    }
161
162    fn as_element(&self) -> Option<ServoLayoutElement<'dom>> {
163        self.node.downcast().map(ServoLayoutElement::from_layout_js)
164    }
165
166    fn as_document(&self) -> Option<ServoLayoutDocument<'dom>> {
167        self.node
168            .downcast()
169            .map(ServoLayoutDocument::from_layout_js)
170    }
171
172    fn as_shadow_root(&self) -> Option<ServoShadowRoot<'dom>> {
173        self.node.downcast().map(ServoShadowRoot::from_layout_js)
174    }
175
176    fn is_in_document(&self) -> bool {
177        unsafe { self.node.get_flag(NodeFlags::IS_IN_A_DOCUMENT_TREE) }
178    }
179}
180
181impl<'dom> LayoutNode<'dom> for ServoLayoutNode<'dom> {
182    type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'dom>;
183
184    fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode {
185        ServoThreadSafeLayoutNode::new(*self)
186    }
187
188    fn type_id(&self) -> LayoutNodeType {
189        NodeTypeIdWrapper(self.script_type_id()).into()
190    }
191
192    unsafe fn initialize_style_and_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
193        let inner = self.get_jsmanaged();
194        if inner.style_data().is_none() {
195            unsafe { inner.initialize_style_data() };
196        }
197        if inner.layout_data().is_none() {
198            unsafe { inner.initialize_layout_data(Box::<RequestedLayoutDataType>::default()) };
199        }
200    }
201
202    fn is_connected(&self) -> bool {
203        unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) }
204    }
205
206    fn style_data(&self) -> Option<&'dom StyleData> {
207        self.get_jsmanaged().style_data()
208    }
209
210    fn layout_data(&self) -> Option<&'dom GenericLayoutData> {
211        self.get_jsmanaged().layout_data()
212    }
213}
214
215/// A wrapper around a `ServoLayoutNode` that can be used safely on different threads.
216/// It's very important that this never mutate anything except this wrapped node and
217/// never access any other node apart from its parent.
218#[derive(Clone, Copy, Debug, PartialEq)]
219pub struct ServoThreadSafeLayoutNode<'dom> {
220    /// The wrapped [`ServoLayoutNode`].
221    pub(super) node: ServoLayoutNode<'dom>,
222
223    /// The possibly nested [`PseudoElementChain`] for this node.
224    pub(super) pseudo_element_chain: PseudoElementChain,
225}
226
227impl<'dom> ServoThreadSafeLayoutNode<'dom> {
228    /// Creates a new `ServoThreadSafeLayoutNode` from the given `ServoLayoutNode`.
229    pub fn new(node: ServoLayoutNode<'dom>) -> Self {
230        ServoThreadSafeLayoutNode {
231            node,
232            pseudo_element_chain: Default::default(),
233        }
234    }
235
236    /// Returns the interior of this node as a `LayoutDom`. This is highly unsafe for layout to
237    /// call and as such is marked `unsafe`.
238    unsafe fn get_jsmanaged(&self) -> LayoutDom<'dom, Node> {
239        self.node.get_jsmanaged()
240    }
241
242    /// Get the first child of this node. Important: this is not safe for
243    /// layout to call, so it should *never* be made public.
244    unsafe fn dangerous_first_child(&self) -> Option<Self> {
245        let js_managed = unsafe { self.get_jsmanaged() };
246        js_managed
247            .first_child_ref()
248            .map(ServoLayoutNode::from_layout_js)
249            .map(Self::new)
250    }
251
252    /// Get the next sibling of this node. Important: this is not safe for
253    /// layout to call, so it should *never* be made public.
254    unsafe fn dangerous_next_sibling(&self) -> Option<Self> {
255        let js_managed = unsafe { self.get_jsmanaged() };
256        js_managed
257            .next_sibling_ref()
258            .map(ServoLayoutNode::from_layout_js)
259            .map(Self::new)
260    }
261
262    /// Whether this is a container for the text within a single-line text input. This
263    /// is used to solve the special case of line height for a text entry widget.
264    /// <https://html.spec.whatwg.org/multipage/#the-input-element-as-a-text-entry-widget>
265    // TODO(stevennovaryo): Remove the addition of HTMLInputElement here once all of the
266    //                      input element is implemented with UA shadow DOM. This is temporary
267    //                      workaround for past version of input element where we are
268    //                      rendering it as a bare html element.
269    pub fn is_single_line_text_input(&self) -> bool {
270        self.type_id() == Some(LayoutNodeType::Element(LayoutElementType::HTMLInputElement)) ||
271            (self.pseudo_element_chain.is_empty() &&
272                self.node.node.is_text_container_of_single_line_input())
273    }
274
275    pub fn selected_style(&self) -> Arc<ComputedValues> {
276        let Some(element) = self.as_element() else {
277            // TODO(stshine): What should the selected style be for text?
278            debug_assert!(self.is_text_node());
279            return self.parent_style();
280        };
281
282        let style_data = &element.style_data().styles;
283        let get_selected_style = || {
284            // This is a workaround for handling the `::selection` pseudos where it would not
285            // propagate to the children and Shadow DOM elements. For this case, UA widget
286            // inner elements should follow the originating element in terms of selection.
287            if self.node.node.is_in_ua_widget() {
288                return Some(element.containing_shadow_host()?.as_node().selected_style());
289            }
290            style_data.pseudos.get(&PseudoElement::Selection).cloned()
291        };
292
293        get_selected_style().unwrap_or_else(|| style_data.primary().clone())
294    }
295}
296
297impl style::dom::NodeInfo for ServoThreadSafeLayoutNode<'_> {
298    fn is_element(&self) -> bool {
299        self.node.is_element()
300    }
301
302    fn is_text_node(&self) -> bool {
303        self.node.is_text_node()
304    }
305}
306
307impl<'dom> ThreadSafeLayoutNode<'dom> for ServoThreadSafeLayoutNode<'dom> {
308    type ConcreteNode = ServoLayoutNode<'dom>;
309    type ConcreteThreadSafeLayoutElement = ServoThreadSafeLayoutElement<'dom>;
310    type ConcreteElement = ServoLayoutElement<'dom>;
311    type ChildrenIterator = ServoThreadSafeLayoutNodeChildrenIterator<'dom>;
312
313    fn opaque(&self) -> style::dom::OpaqueNode {
314        unsafe { self.get_jsmanaged().opaque() }
315    }
316
317    fn pseudo_element_chain(&self) -> PseudoElementChain {
318        self.pseudo_element_chain
319    }
320
321    fn type_id(&self) -> Option<LayoutNodeType> {
322        if self.pseudo_element_chain.is_empty() {
323            Some(self.node.type_id())
324        } else {
325            None
326        }
327    }
328
329    fn parent_style(&self) -> Arc<ComputedValues> {
330        let parent_element = self.node.traversal_parent().unwrap();
331        let parent_data = parent_element.borrow_data().unwrap();
332        parent_data.styles.primary().clone()
333    }
334
335    fn initialize_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
336        let inner = self.node.get_jsmanaged();
337        if inner.layout_data().is_none() {
338            unsafe {
339                inner.initialize_layout_data(Box::<RequestedLayoutDataType>::default());
340            }
341        }
342    }
343
344    fn debug_id(self) -> usize {
345        self.node.debug_id()
346    }
347
348    fn children(&self) -> style::dom::LayoutIterator<Self::ChildrenIterator> {
349        style::dom::LayoutIterator(ServoThreadSafeLayoutNodeChildrenIterator::new(*self))
350    }
351
352    fn as_element(&self) -> Option<ServoThreadSafeLayoutElement<'dom>> {
353        self.node
354            .as_element()
355            .map(|el| ServoThreadSafeLayoutElement {
356                element: el,
357                pseudo_element_chain: self.pseudo_element_chain,
358            })
359    }
360
361    fn as_html_element(&self) -> Option<ServoThreadSafeLayoutElement<'dom>> {
362        self.as_element()
363            .filter(|element| element.element.is_html_element())
364    }
365
366    fn style_data(&self) -> Option<&'dom StyleData> {
367        self.node.style_data()
368    }
369
370    fn layout_data(&self) -> Option<&'dom GenericLayoutData> {
371        self.node.layout_data()
372    }
373
374    fn unsafe_get(self) -> Self::ConcreteNode {
375        self.node
376    }
377
378    fn text_content(self) -> Cow<'dom, str> {
379        unsafe { self.get_jsmanaged().text_content() }
380    }
381
382    fn selection(&self) -> Option<SharedSelection> {
383        let this = unsafe { self.get_jsmanaged() };
384        this.selection()
385    }
386
387    fn image_url(&self) -> Option<ServoUrl> {
388        let this = unsafe { self.get_jsmanaged() };
389        this.image_url()
390    }
391
392    fn image_density(&self) -> Option<f64> {
393        let this = unsafe { self.get_jsmanaged() };
394        this.image_density()
395    }
396
397    fn showing_broken_image_icon(&self) -> bool {
398        let this = unsafe { self.get_jsmanaged() };
399        this.showing_broken_image_icon()
400    }
401
402    fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
403        let this = unsafe { self.get_jsmanaged() };
404        this.image_data()
405    }
406
407    fn canvas_data(&self) -> Option<HTMLCanvasData> {
408        let this = unsafe { self.get_jsmanaged() };
409        this.canvas_data()
410    }
411
412    fn media_data(&self) -> Option<HTMLMediaData> {
413        let this = unsafe { self.get_jsmanaged() };
414        this.media_data()
415    }
416
417    fn svg_data(&self) -> Option<SVGElementData<'dom>> {
418        let this = unsafe { self.get_jsmanaged() };
419        this.svg_data()
420    }
421
422    // Can return None if the iframe has no nested browsing context
423    fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId> {
424        let this = unsafe { self.get_jsmanaged() };
425        this.iframe_browsing_context_id()
426    }
427
428    // Can return None if the iframe has no nested browsing context
429    fn iframe_pipeline_id(&self) -> Option<PipelineId> {
430        let this = unsafe { self.get_jsmanaged() };
431        this.iframe_pipeline_id()
432    }
433
434    fn get_span(&self) -> Option<u32> {
435        unsafe {
436            self.get_jsmanaged()
437                .downcast::<Element>()
438                .unwrap()
439                .get_span()
440        }
441    }
442
443    fn get_colspan(&self) -> Option<u32> {
444        unsafe {
445            self.get_jsmanaged()
446                .downcast::<Element>()
447                .unwrap()
448                .get_colspan()
449        }
450    }
451
452    fn get_rowspan(&self) -> Option<u32> {
453        unsafe {
454            self.get_jsmanaged()
455                .downcast::<Element>()
456                .unwrap()
457                .get_rowspan()
458        }
459    }
460
461    fn with_pseudo_element_chain(&self, pseudo_element_chain: PseudoElementChain) -> Self {
462        Self {
463            node: self.node,
464            pseudo_element_chain,
465        }
466    }
467
468    /// # Safety
469    ///
470    /// This function accesses and modifies the underlying DOM object and should
471    /// not be used by more than a single thread at once.
472    fn set_uses_content_attribute_with_attr(&self, uses_content_attribute_with_attr: bool) {
473        unsafe {
474            self.node.node.set_flag(
475                NodeFlags::USES_ATTR_IN_CONTENT_ATTRIBUTE,
476                uses_content_attribute_with_attr,
477            )
478        }
479    }
480}
481
482pub enum ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
483    /// Iterating over the children of a node
484    Node(Option<ServoThreadSafeLayoutNode<'dom>>),
485    /// Iterating over the assigned nodes of a `HTMLSlotElement`
486    Slottables(<Vec<ServoLayoutNode<'dom>> as IntoIterator>::IntoIter),
487}
488
489impl<'dom> ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
490    #[expect(unsafe_code)]
491    fn new(
492        parent: ServoThreadSafeLayoutNode<'dom>,
493    ) -> ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
494        if let Some(element) = parent.as_element() {
495            if let Some(shadow) = element.shadow_root() {
496                return Self::new(shadow.as_node().to_threadsafe());
497            };
498
499            let slotted_nodes = element.slotted_nodes();
500            if !slotted_nodes.is_empty() {
501                #[expect(clippy::unnecessary_to_owned)] // Clippy is wrong.
502                return Self::Slottables(slotted_nodes.to_owned().into_iter());
503            }
504        }
505
506        Self::Node(unsafe { parent.dangerous_first_child() })
507    }
508}
509
510impl<'dom> Iterator for ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
511    type Item = ServoThreadSafeLayoutNode<'dom>;
512
513    fn next(&mut self) -> Option<Self::Item> {
514        match self {
515            Self::Node(node) => {
516                let next_sibling = unsafe { (*node)?.dangerous_next_sibling() };
517                std::mem::replace(node, next_sibling)
518            },
519            Self::Slottables(slots) => slots.next().map(|node| node.to_threadsafe()),
520        }
521    }
522}
523
524impl FusedIterator for ServoThreadSafeLayoutNodeChildrenIterator<'_> {}