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