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