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