Skip to main content

layout/fragment_tree/
base_fragment.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
5use std::sync::atomic::{AtomicU8, Ordering};
6
7use app_units::Au;
8use atomic_refcell::AtomicRef;
9use bitflags::bitflags;
10use layout_api::{LayoutElement, LayoutNode, PseudoElementChain, combine_id_with_fragment_type};
11use malloc_size_of::malloc_size_of_is_0;
12use malloc_size_of_derive::MallocSizeOf;
13use num_derive::FromPrimitive;
14use num_traits::FromPrimitive;
15use script::layout_dom::ServoLayoutNode;
16use servo_arc::Arc as ServoArc;
17use style::dom::OpaqueNode;
18use style::properties::ComputedValues;
19use style::selector_parser::PseudoElement;
20use web_atoms::local_name;
21
22use crate::SharedStyle;
23use crate::dom_traversal::NodeAndStyleInfo;
24use crate::geom::{PhysicalPoint, PhysicalRect, PhysicalSize, SyncPhysicalRectAu};
25
26#[derive(Clone, Debug, Default, FromPrimitive, MallocSizeOf, PartialEq)]
27#[repr(u8)]
28pub(crate) enum FragmentStatus {
29    /// This is a brand new fragment.
30    #[default]
31    New,
32    /// The style of the fragment has changed.
33    StyleChanged,
34    /// The fragment was reused between layouts, some descendant fragment may be different,
35    /// but otherwise nothing has changed on the fragment itself.
36    OnlyDescendantsChanged,
37    /// The fragment hasn't changed.
38    Clean,
39}
40
41/// This data structure stores fields that are common to all non-base
42/// Fragment types and should generally be the first member of all
43/// concrete fragments.
44#[derive(MallocSizeOf)]
45pub(crate) struct BaseFragment {
46    /// A tag which identifies the DOM node and pseudo element of this
47    /// Fragment's content. If this fragment is for an anonymous box,
48    /// the tag will be None.
49    pub tag: Option<Tag>,
50
51    /// Flags which various information about this fragment used during
52    /// layout.
53    pub flags: FragmentFlags,
54
55    /// The style for this [`BaseFragment`]. Depending on the fragment type this is either
56    /// a shared or non-shared style.
57    pub style: SharedStyle,
58
59    /// The content rect of this fragment in the parent fragment's content rectangle. This
60    /// does not include padding, border, or margin -- it only includes content. This is
61    /// relative to the parent containing block.
62    rect: SyncPhysicalRectAu,
63
64    /// A [`FragmentStatus`] used to track fragment reuse when collecting reflow statistics.
65    pub status: AtomicU8,
66}
67
68impl std::fmt::Debug for BaseFragment {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        let mut formatter = f.debug_struct("BaseFragment");
71        let mut formatter = formatter.field("tag", &self.tag);
72        if !self.flags.is_empty() {
73            formatter = formatter.field("flags", &self.flags);
74        }
75        formatter
76            .field("rect", &self.rect())
77            .field("status", &self.status())
78            .finish()
79    }
80}
81
82impl BaseFragment {
83    pub(crate) fn new(
84        base_fragment_info: BaseFragmentInfo,
85        style: SharedStyle,
86        rect: PhysicalRect<Au>,
87    ) -> Self {
88        Self {
89            tag: base_fragment_info.tag,
90            flags: base_fragment_info.flags,
91            style,
92            rect: SyncPhysicalRectAu::new(rect),
93            status: AtomicU8::new(FragmentStatus::New as u8),
94        }
95    }
96
97    #[inline]
98    pub(crate) fn rect(&self) -> PhysicalRect<Au> {
99        self.rect.get()
100    }
101
102    #[inline]
103    pub(crate) fn set_rect(&self, new_rect: PhysicalRect<Au>) {
104        self.rect.set(new_rect);
105    }
106
107    #[inline]
108    pub(crate) fn translate_rect(&self, offset: PhysicalSize<Au>) {
109        self.rect.translate(offset)
110    }
111
112    #[inline]
113    pub(crate) fn set_rect_origin(&self, offset: PhysicalPoint<Au>) {
114        self.rect.set_origin(offset)
115    }
116
117    pub(crate) fn is_anonymous(&self) -> bool {
118        self.tag.is_none()
119    }
120
121    pub(crate) fn status(&self) -> FragmentStatus {
122        FragmentStatus::from_u8(self.status.load(Ordering::Relaxed))
123            .expect("Unknown FragmentStatus value")
124    }
125
126    pub(crate) fn set_status(&self, new_status: FragmentStatus) {
127        self.status.store(new_status as u8, Ordering::Relaxed)
128    }
129
130    pub(crate) fn repair_style(&self, style: &ServoArc<ComputedValues>) {
131        *self.style.borrow_mut() = style.clone();
132        self.set_status(FragmentStatus::StyleChanged);
133    }
134
135    pub(crate) fn style<'a>(&'a self) -> AtomicRef<'a, ServoArc<ComputedValues>> {
136        self.style.borrow()
137    }
138}
139
140/// Information necessary to construct a new BaseFragment.
141#[derive(Clone, Copy, Debug, MallocSizeOf)]
142pub(crate) struct BaseFragmentInfo {
143    /// The tag to use for the new BaseFragment, if it is not an anonymous Fragment.
144    pub tag: Option<Tag>,
145
146    /// The flags to use for the new BaseFragment.
147    pub flags: FragmentFlags,
148}
149
150impl BaseFragmentInfo {
151    pub(crate) fn anonymous() -> Self {
152        Self {
153            tag: None,
154            flags: FragmentFlags::empty(),
155        }
156    }
157
158    pub(crate) fn new_for_testing(id: usize) -> Self {
159        Self {
160            tag: Some(Tag {
161                node: OpaqueNode(id),
162                pseudo_element_chain: Default::default(),
163            }),
164            flags: FragmentFlags::empty(),
165        }
166    }
167
168    pub(crate) fn is_anonymous(&self) -> bool {
169        self.tag.is_none()
170    }
171}
172
173impl From<&NodeAndStyleInfo<'_>> for BaseFragmentInfo {
174    fn from(info: &NodeAndStyleInfo) -> Self {
175        info.node.into()
176    }
177}
178
179impl From<ServoLayoutNode<'_>> for BaseFragmentInfo {
180    fn from(node: ServoLayoutNode) -> Self {
181        let pseudo_element_chain = node.pseudo_element_chain();
182        let mut flags = FragmentFlags::empty();
183
184        // Anonymous boxes should not have a tag, because they should not take part in hit testing.
185        //
186        // TODO(mrobinson): It seems that anonymous boxes should take part in hit testing in some
187        // cases, but currently this means that the order of hit test results isn't as expected for
188        // some WPT tests. This needs more investigation.
189        if matches!(
190            pseudo_element_chain.innermost(),
191            Some(PseudoElement::ServoAnonymousBox) |
192                Some(PseudoElement::ServoAnonymousTable) |
193                Some(PseudoElement::ServoAnonymousTableCell) |
194                Some(PseudoElement::ServoAnonymousTableRow)
195        ) {
196            return Self::anonymous();
197        }
198
199        if node.as_element().is_some_and(|element| element.is_root()) {
200            flags.insert(FragmentFlags::IS_ROOT_ELEMENT);
201        }
202
203        if let Some(element) = node.as_html_element() {
204            if element.is_body_element_of_html_element_root() {
205                flags.insert(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT);
206            }
207            match element.local_name() {
208                &local_name!("br") => {
209                    flags.insert(FragmentFlags::IS_BR_ELEMENT);
210                },
211                &local_name!("table") | &local_name!("th") | &local_name!("td") => {
212                    flags.insert(FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT);
213                },
214                &local_name!("input") => {
215                    flags.insert(FragmentFlags::IS_INPUT_ELEMENT);
216                },
217                _ => {},
218            }
219        };
220
221        Self {
222            tag: Some(node.into()),
223            flags,
224        }
225    }
226}
227
228bitflags! {
229    /// Flags used to track various information about a DOM node during layout.
230    #[derive(Clone, Copy, Debug)]
231    pub(crate) struct FragmentFlags: u16 {
232        /// Whether or not the node that created this fragment is a `<body>` element on an HTML document.
233        const IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT = 1 << 0;
234        /// Whether or not the node that created this Fragment is a `<br>` element.
235        const IS_BR_ELEMENT = 1 << 1;
236        /// Whether or not the node that created this Fragment is a widget. Widgets behave similarly to
237        /// replaced elements, e.g. they are atomic when inline-level, and their automatic inline size
238        /// doesn't stretch when block-level.
239        /// <https://drafts.csswg.org/css-ui/#widget>
240        const IS_WIDGET = 1 << 2;
241        /// Whether or not this Fragment is a flex item or a grid item.
242        const IS_FLEX_OR_GRID_ITEM = 1 << 3;
243        /// Whether or not this Fragment was created to contain a replaced element or is
244        /// a replaced element.
245        const IS_REPLACED = 1 << 4;
246        /// Whether or not the node that created was a `<table>`, `<th>` or
247        /// `<td>` element. Note that this does *not* include elements with
248        /// `display: table` or `display: table-cell`.
249        const IS_TABLE_TH_OR_TD_ELEMENT = 1 << 5;
250        /// Whether or not this Fragment was created to contain a list item marker
251        /// with a used value of `list-style-position: outside`.
252        const IS_OUTSIDE_LIST_ITEM_MARKER = 1 << 6;
253        /// Avoid painting the borders, backgrounds, and drop shadow for this fragment, this is used
254        /// for empty table cells when 'empty-cells' is 'hide' and also table wrappers.  This flag
255        /// doesn't avoid hit-testing nor does it prevent the painting outlines.
256        const DO_NOT_PAINT = 1 << 7;
257        /// Whether or not the size of this fragment depends on the block size of its container
258        /// and the fragment can be a flex item. This flag is used to cache items during flex
259        /// layout.
260        const SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM = 1 << 8;
261        /// Whether or not the node that created this fragment is the root element.
262        const IS_ROOT_ELEMENT = 1 << 9;
263        /// If element has propagated the overflow value to viewport.
264        const PROPAGATED_OVERFLOW_TO_VIEWPORT = 1 << 10;
265        /// Whether or not this is a table cell that is part of a collapsed row or column.
266        /// In that case it should not be painted.
267        const IS_COLLAPSED = 1 << 11;
268        /// Whether or not the node that created this Fragment is a `<input>` element.
269        const IS_INPUT_ELEMENT = 1 << 12;
270
271    }
272}
273
274malloc_size_of_is_0!(FragmentFlags);
275
276/// A data structure used to hold DOM and pseudo-element information about
277/// a particular layout object.
278#[derive(Clone, Copy, Eq, MallocSizeOf, PartialEq)]
279pub(crate) struct Tag {
280    pub(crate) node: OpaqueNode,
281    pub(crate) pseudo_element_chain: PseudoElementChain,
282}
283
284impl std::fmt::Debug for Tag {
285    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286        f.write_fmt(format_args!("Tag({:?}", self.node))?;
287        if let Some(pseudo) = self.pseudo_element_chain.primary {
288            f.write_fmt(format_args!(", PseudoElement::{pseudo:?}"))?;
289        }
290        if let Some(pseudo) = self.pseudo_element_chain.secondary {
291            f.write_fmt(format_args!(", PseudoElement::{pseudo:?}"))?;
292        }
293        f.write_str(")")
294    }
295}
296
297impl Tag {
298    pub(crate) fn to_display_list_fragment_id(self) -> u64 {
299        combine_id_with_fragment_type(self.node.id(), self.pseudo_element_chain.primary.into())
300    }
301}
302
303impl From<ServoLayoutNode<'_>> for Tag {
304    fn from(node: ServoLayoutNode<'_>) -> Self {
305        Self {
306            node: node.opaque(),
307            pseudo_element_chain: node.pseudo_element_chain(),
308        }
309    }
310}