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