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