Skip to main content

script/dom/node/
layout_dom.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//! Methods for layout of node
6
7use atomic_refcell::AtomicRef;
8use layout_api::{
9    GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType,
10    SVGElementData,
11};
12use net_traits::image_cache::Image;
13use pixels::ImageMetadata;
14use script_bindings::codegen::InheritTypes::{
15    ElementTypeId, HTMLElementTypeId, SVGElementTypeId, SVGGraphicsElementTypeId,
16};
17use servo_base::cross_process_instant::CrossProcessInstant;
18use servo_base::id::{BrowsingContextId, PipelineId};
19use servo_base::text::{RangeAny, Utf16CodeUnits, Utf32CodeUnits};
20use servo_url::ServoUrl;
21use style::dom::OpaqueNode;
22use style::selector_parser::PseudoElement;
23
24use crate::dom::bindings::inheritance::{CharacterDataTypeId, NodeTypeId};
25use crate::dom::bindings::root::{LayoutDom, ToLayout, ToLayoutOptional};
26use crate::dom::document::Document;
27use crate::dom::element::Element;
28use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
29use crate::dom::html::htmlcanvaselement::HTMLCanvasElement;
30use crate::dom::html::htmliframeelement::HTMLIFrameElement;
31use crate::dom::html::htmlimageelement::HTMLImageElement;
32use crate::dom::html::htmlslotelement::HTMLSlotElement;
33use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
34use crate::dom::html::htmlvideoelement::HTMLVideoElement;
35use crate::dom::shadowroot::shadowroot::ShadowRoot;
36use crate::dom::svg::svgsvgelement::SVGSVGElement;
37use crate::dom::text::Text;
38use crate::dom::{Node, NodeFlags};
39
40impl<'dom> LayoutDom<'dom, Node> {
41    #[inline]
42    #[expect(unsafe_code)]
43    pub(crate) fn parent_node_ref(self) -> Option<LayoutDom<'dom, Node>> {
44        unsafe { self.unsafe_get().parent_node().to_layout() }
45    }
46
47    #[inline]
48    pub(crate) fn type_id_for_layout(self) -> NodeTypeId {
49        self.unsafe_get().type_id()
50    }
51
52    #[inline]
53    pub(crate) fn is_element_for_layout(&self) -> bool {
54        (*self).is::<Element>()
55    }
56
57    pub(crate) fn is_text_node_for_layout(&self) -> bool {
58        matches!(
59            self.type_id_for_layout(),
60            NodeTypeId::CharacterData(CharacterDataTypeId::Text(..))
61        )
62    }
63
64    #[inline]
65    pub(crate) fn composed_parent_node_ref(self) -> Option<LayoutDom<'dom, Node>> {
66        let parent = self.parent_node_ref();
67        if let Some(parent) = parent &&
68            let Some(shadow_root) = parent.downcast::<ShadowRoot>()
69        {
70            return Some(shadow_root.get_host_for_layout().upcast());
71        }
72        parent
73    }
74
75    #[inline]
76    pub(crate) fn traversal_parent(self) -> Option<LayoutDom<'dom, Element>> {
77        if let Some(assigned_slot) = self.assigned_slot_for_layout() {
78            return Some(assigned_slot.upcast());
79        }
80        let parent = self.parent_node_ref()?;
81        if let Some(shadow) = parent.downcast::<ShadowRoot>() {
82            return Some(shadow.get_host_for_layout());
83        };
84        parent.downcast()
85    }
86
87    #[inline]
88    #[expect(unsafe_code)]
89    pub(crate) fn first_child_ref(self) -> Option<LayoutDom<'dom, Node>> {
90        unsafe { self.unsafe_get().first_child().to_layout() }
91    }
92
93    #[inline]
94    #[expect(unsafe_code)]
95    pub(crate) fn last_child_ref(self) -> Option<LayoutDom<'dom, Node>> {
96        unsafe { self.unsafe_get().last_child().to_layout() }
97    }
98
99    #[inline]
100    #[expect(unsafe_code)]
101    pub(crate) fn prev_sibling_ref(self) -> Option<LayoutDom<'dom, Node>> {
102        unsafe { self.unsafe_get().prev_sibling().to_layout() }
103    }
104
105    #[inline]
106    #[expect(unsafe_code)]
107    pub(crate) fn next_sibling_ref(self) -> Option<LayoutDom<'dom, Node>> {
108        unsafe { self.unsafe_get().next_sibling().to_layout() }
109    }
110
111    #[inline]
112    #[expect(unsafe_code)]
113    pub(crate) fn owner_doc_for_layout(self) -> LayoutDom<'dom, Document> {
114        unsafe { self.unsafe_get().get_owner_doc().to_layout().unwrap() }
115    }
116
117    #[inline]
118    #[expect(unsafe_code)]
119    pub(crate) fn containing_shadow_root_for_layout(self) -> Option<LayoutDom<'dom, ShadowRoot>> {
120        unsafe {
121            self.unsafe_get()
122                .get_rare_data()
123                .borrow_for_layout()
124                .as_ref()?
125                .containing_shadow_root
126                .as_ref()
127                .map(|sr| sr.to_layout())
128        }
129    }
130
131    #[inline]
132    #[expect(unsafe_code)]
133    pub(crate) fn assigned_slot_for_layout(self) -> Option<LayoutDom<'dom, HTMLSlotElement>> {
134        unsafe {
135            self.unsafe_get()
136                .get_rare_data()
137                .borrow_for_layout()
138                .as_ref()?
139                .slottable_data
140                .assigned_slot
141                .as_ref()
142                .map(|assigned_slot| assigned_slot.to_layout())
143        }
144    }
145
146    // FIXME(nox): get_flag/set_flag (especially the latter) are not safe because
147    // they mutate stuff while values of this type can be used from multiple
148    // threads at once, this should be revisited.
149
150    #[inline]
151    #[expect(unsafe_code)]
152    pub(crate) unsafe fn get_flag(self, flag: NodeFlags) -> bool {
153        (self.unsafe_get()).flags().get().contains(flag)
154    }
155
156    #[inline]
157    #[expect(unsafe_code)]
158    pub(crate) unsafe fn set_flag(self, flag: NodeFlags, value: bool) {
159        let this = self.unsafe_get();
160        let mut flags = (this).flags().get();
161
162        if value {
163            flags.insert(flag);
164        } else {
165            flags.remove(flag);
166        }
167
168        (this).flags().set(flags);
169    }
170
171    #[inline]
172    #[expect(unsafe_code)]
173    pub(crate) fn layout_data(self) -> Option<&'dom GenericLayoutData> {
174        unsafe {
175            self.unsafe_get()
176                .layout_data()
177                .borrow_for_layout()
178                .as_deref()
179        }
180    }
181
182    /// Initialize the style data of this node.
183    ///
184    /// # Safety
185    ///
186    /// This method is unsafe because it modifies the given node during
187    /// layout. Callers should ensure that no other layout thread is
188    /// attempting to read or modify the opaque layout data of this node.
189    #[inline]
190    #[expect(unsafe_code)]
191    pub(crate) unsafe fn initialize_layout_data(self, new_data: Box<GenericLayoutData>) {
192        let data = unsafe { self.unsafe_get().layout_data().borrow_mut_for_layout() };
193        debug_assert!(data.is_none());
194        *data = Some(new_data);
195    }
196
197    /// Clear the style and opaque layout data of this node.
198    ///
199    /// # Safety
200    ///
201    /// This method is unsafe because it modifies the given node during
202    /// layout. Callers should ensure that no other layout thread is
203    /// attempting to read or modify the opaque layout data of this node.
204    #[inline]
205    #[expect(unsafe_code)]
206    pub(crate) unsafe fn clear_layout_data(self) {
207        unsafe {
208            self.unsafe_get()
209                .layout_data()
210                .borrow_mut_for_layout()
211                .take();
212        }
213    }
214
215    /// Whether this element serve as a container of editable text for a text input
216    /// that is implemented as an UA widget.
217    pub(crate) fn is_single_line_text_inner_editor(&self) -> bool {
218        matches!(
219            self.implemented_pseudo_element(),
220            Some(PseudoElement::ServoTextControlInnerEditor)
221        )
222    }
223
224    /// Whether this element serve as a container of any text inside a text input
225    /// that is implemented as an UA widget.
226    pub(crate) fn is_text_container_of_single_line_input(&self) -> bool {
227        let is_single_line_text_inner_placeholder = matches!(
228            self.implemented_pseudo_element(),
229            Some(PseudoElement::Placeholder)
230        );
231        // Currently `::placeholder` is only implemented for single line text input element.
232        debug_assert!(
233            !is_single_line_text_inner_placeholder ||
234                self.containing_shadow_root_for_layout()
235                    .map(|root| root.get_host_for_layout())
236                    .map(|host| host.downcast::<HTMLInputElement>())
237                    .is_some()
238        );
239
240        self.is_single_line_text_inner_editor() || is_single_line_text_inner_placeholder
241    }
242
243    pub(crate) fn text_content(self) -> AtomicRef<'dom, str> {
244        self.downcast::<Text>()
245            .expect("Called LayoutDom::text_content on non-Text node!")
246            .upcast()
247            .data_for_layout()
248    }
249
250    #[expect(unsafe_code)]
251    pub(crate) fn selection_for_text_node(&self) -> Option<RangeAny<Utf32CodeUnits>> {
252        if let Some(shadow_root) = self.containing_shadow_root_for_layout() {
253            let host = shadow_root.get_host_for_layout();
254            if let Some(input) = host.downcast::<HTMLInputElement>() {
255                return input.selection_for_layout();
256            }
257            if let Some(textarea) = host.downcast::<HTMLTextAreaElement>() {
258                return textarea.selection_for_layout();
259            }
260        }
261
262        let unsafe_self = self.unsafe_get();
263        if !unsafe_self.get_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION) ||
264            unsafe_self.get_flag(NodeFlags::SELECTION_INHIBITED)
265        {
266            return None;
267        }
268
269        let text_node = self.downcast::<Text>()?;
270        let text = text_node.upcast().data_for_layout();
271        let selection = self.owner_doc_for_layout().visible_selection_for_layout()?;
272        let range = selection.range_for_layout();
273
274        let range = range.as_ref()?;
275        let is_start_node = unsafe { range.start.container.to_layout() } == *self;
276        let is_end_node = unsafe { range.end.container.to_layout() } == *self;
277
278        let start =
279            is_start_node.then(|| Utf16CodeUnits(range.start.offset).to_utf32_code_units_in(&text));
280        let end =
281            is_end_node.then(|| Utf16CodeUnits(range.end.offset).to_utf32_code_units_in(&text));
282        Some(RangeAny::new(start, end))
283    }
284
285    pub(crate) fn text_node_paints_caret(&self) -> bool {
286        // TODO: Add support for `contenteditable`.
287        self.parent_node_ref()
288            .is_some_and(|parent| parent.is_text_container_of_single_line_input())
289    }
290
291    pub(crate) fn text_node_needs_caret_placeholder(&self) -> bool {
292        // TODO: Add support for `contenteditable`.
293        self.parent_node_ref()
294            .is_some_and(|parent| parent.is_text_container_of_single_line_input())
295    }
296
297    pub(crate) fn replaced_is_selected(&self) -> bool {
298        let unsafe_self = self.unsafe_get();
299        unsafe_self.get_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION) &&
300            !unsafe_self.get_flag(NodeFlags::SELECTION_INHIBITED)
301    }
302
303    pub(crate) fn image_url(self) -> Option<ServoUrl> {
304        self.downcast::<HTMLImageElement>()
305            .expect("not an image!")
306            .image_url()
307    }
308
309    pub(crate) fn image_data(self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
310        self.downcast::<HTMLImageElement>().map(|e| e.image_data())
311    }
312
313    pub(crate) fn load_time(self) -> Option<CrossProcessInstant> {
314        self.downcast::<HTMLImageElement>()
315            .and_then(|image_element| image_element.load_time())
316    }
317
318    pub(crate) fn image_density(self) -> Option<f64> {
319        self.downcast::<HTMLImageElement>()
320            .expect("not an image!")
321            .image_density()
322    }
323
324    pub(crate) fn showing_broken_image_icon(self) -> bool {
325        self.downcast::<HTMLImageElement>()
326            .map(|image_element| image_element.showing_broken_image_icon())
327            .unwrap_or_default()
328    }
329
330    pub(crate) fn canvas_data(self) -> Option<HTMLCanvasData> {
331        self.downcast::<HTMLCanvasElement>()
332            .map(|canvas| canvas.data())
333    }
334
335    pub(crate) fn media_data(self) -> Option<HTMLMediaData> {
336        self.downcast::<HTMLVideoElement>()
337            .map(|media| media.data())
338    }
339
340    pub(crate) fn svg_data(self) -> Option<SVGElementData<'dom>> {
341        self.downcast::<SVGSVGElement>().map(|svg| svg.data())
342    }
343
344    pub(crate) fn iframe_browsing_context_id(self) -> Option<BrowsingContextId> {
345        self.downcast::<HTMLIFrameElement>()
346            .and_then(|iframe_element| iframe_element.browsing_context_id())
347    }
348
349    pub(crate) fn iframe_pipeline_id(self) -> Option<PipelineId> {
350        self.downcast::<HTMLIFrameElement>()
351            .and_then(|iframe_element| iframe_element.pipeline_id())
352    }
353
354    #[expect(unsafe_code)]
355    pub(crate) fn opaque(self) -> OpaqueNode {
356        unsafe { OpaqueNode(self.get_jsobject() as usize) }
357    }
358
359    #[expect(unsafe_code)]
360    pub(crate) fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
361        unsafe {
362            self.unsafe_get()
363                .get_rare_data()
364                .borrow_for_layout()
365                .as_ref()
366                .and_then(|rare_data| rare_data.implemented_pseudo_element)
367        }
368    }
369
370    pub(crate) fn is_in_ua_widget(&self) -> bool {
371        self.unsafe_get().is_in_ua_widget()
372    }
373
374    pub(crate) fn is_root_of_user_agent_widget(&self) -> bool {
375        self.downcast::<Element>().is_some_and(|element| {
376            element
377                .get_shadow_root_for_layout()
378                .is_some_and(|shadow_root| shadow_root.is_user_agent_widget())
379        })
380    }
381
382    pub(crate) fn children_count(&self) -> u32 {
383        self.unsafe_get().children_count()
384    }
385}
386
387pub(crate) struct NodeTypeIdWrapper(pub(crate) NodeTypeId);
388
389impl From<NodeTypeIdWrapper> for LayoutNodeType {
390    #[inline(always)]
391    fn from(node_type: NodeTypeIdWrapper) -> LayoutNodeType {
392        match node_type.0 {
393            NodeTypeId::Element(e) => LayoutNodeType::Element(ElementTypeIdWrapper(e).into()),
394            NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => LayoutNodeType::Text,
395            x => unreachable!("Layout should not traverse nodes of type {:?}", x),
396        }
397    }
398}
399
400struct ElementTypeIdWrapper(ElementTypeId);
401
402impl From<ElementTypeIdWrapper> for LayoutElementType {
403    #[inline(always)]
404    fn from(element_type: ElementTypeIdWrapper) -> LayoutElementType {
405        match element_type.0 {
406            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBodyElement) => {
407                LayoutElementType::HTMLBodyElement
408            },
409            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement) => {
410                LayoutElementType::HTMLButtonElement
411            },
412            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBRElement) => {
413                LayoutElementType::HTMLBRElement
414            },
415            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLCanvasElement) => {
416                LayoutElementType::HTMLCanvasElement
417            },
418            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLHtmlElement) => {
419                LayoutElementType::HTMLHtmlElement
420            },
421            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement) => {
422                LayoutElementType::HTMLIFrameElement
423            },
424            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement) => {
425                LayoutElementType::HTMLImageElement
426            },
427            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLMediaElement(_)) => {
428                LayoutElementType::HTMLMediaElement
429            },
430            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement) => {
431                LayoutElementType::HTMLInputElement
432            },
433            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptGroupElement) => {
434                LayoutElementType::HTMLOptGroupElement
435            },
436            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptionElement) => {
437                LayoutElementType::HTMLOptionElement
438            },
439            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement) => {
440                LayoutElementType::HTMLObjectElement
441            },
442            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLParagraphElement) => {
443                LayoutElementType::HTMLParagraphElement
444            },
445            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLPreElement) => {
446                LayoutElementType::HTMLPreElement
447            },
448            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement) => {
449                LayoutElementType::HTMLSelectElement
450            },
451            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableCellElement) => {
452                LayoutElementType::HTMLTableCellElement
453            },
454            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableColElement) => {
455                LayoutElementType::HTMLTableColElement
456            },
457            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableElement) => {
458                LayoutElementType::HTMLTableElement
459            },
460            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableRowElement) => {
461                LayoutElementType::HTMLTableRowElement
462            },
463            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableSectionElement) => {
464                LayoutElementType::HTMLTableSectionElement
465            },
466            ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement) => {
467                LayoutElementType::HTMLTextAreaElement
468            },
469            ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement(
470                SVGGraphicsElementTypeId::SVGImageElement,
471            )) => LayoutElementType::SVGImageElement,
472            ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement(
473                SVGGraphicsElementTypeId::SVGSVGElement,
474            )) => LayoutElementType::SVGSVGElement,
475            _ => LayoutElementType::Element,
476        }
477    }
478}