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