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::{AtomicI32, AtomicU8, Ordering};
6
7use app_units::Au;
8use atomic_refcell::AtomicRef;
9use bitflags::bitflags;
10use euclid::{Point2D, Rect, Size2D};
11use layout_api::{LayoutElement, LayoutNode, PseudoElementChain, combine_id_with_fragment_type};
12use malloc_size_of::malloc_size_of_is_0;
13use malloc_size_of_derive::MallocSizeOf;
14use num_derive::FromPrimitive;
15use num_traits::FromPrimitive;
16use script::layout_dom::ServoLayoutNode;
17use servo_arc::Arc as ServoArc;
18use style::dom::OpaqueNode;
19use style::properties::ComputedValues;
20use style::selector_parser::PseudoElement;
21use style_traits::CSSPixel;
22use web_atoms::local_name;
23
24use crate::SharedStyle;
25use crate::dom_traversal::NodeAndStyleInfo;
26use crate::geom::{PhysicalPoint, PhysicalRect, PhysicalSize};
27
28#[derive(Clone, Debug, Default, FromPrimitive, MallocSizeOf, PartialEq)]
29#[repr(u8)]
30pub(crate) enum FragmentStatus {
31    /// This is a brand new fragment.
32    #[default]
33    New,
34    /// The style of the fragment has changed.
35    StyleChanged,
36    /// The fragment was reused between layouts, some descendant fragment may be different,
37    /// but otherwise nothing has changed on the fragment itself.
38    OnlyDescendantsChanged,
39    /// The fragment hasn't changed.
40    Clean,
41}
42
43/// This data structure stores fields that are common to all non-base
44/// Fragment types and should generally be the first member of all
45/// concrete fragments.
46#[derive(MallocSizeOf)]
47pub(crate) struct BaseFragment {
48    /// A tag which identifies the DOM node and pseudo element of this
49    /// Fragment's content. If this fragment is for an anonymous box,
50    /// the tag will be None.
51    pub tag: Option<Tag>,
52
53    /// Flags which various information about this fragment used during
54    /// layout.
55    pub flags: FragmentFlags,
56
57    /// The style for this [`BaseFragment`]. Depending on the fragment type this is either
58    /// a shared or non-shared style.
59    pub style: SharedStyle,
60
61    /// The content rect of this fragment in the parent fragment's content rectangle. This
62    /// does not include padding, border, or margin -- it only includes content. This is
63    /// relative to the parent containing block.
64    rect: Rect<AtomicI32, CSSPixel>,
65
66    /// A [`FragmentStatus`] used to track fragment reuse when collecting reflow statistics.
67    pub status: AtomicU8,
68}
69
70impl std::fmt::Debug for BaseFragment {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        let mut formatter = f.debug_struct("BaseFragment");
73        let mut formatter = formatter.field("tag", &self.tag);
74        if !self.flags.is_empty() {
75            formatter = formatter.field("flags", &self.flags);
76        }
77        formatter
78            .field("rect", &self.rect())
79            .field("status", &self.status())
80            .finish()
81    }
82}
83
84impl BaseFragment {
85    pub(crate) fn new(
86        base_fragment_info: BaseFragmentInfo,
87        style: SharedStyle,
88        rect: PhysicalRect<Au>,
89    ) -> Self {
90        Self {
91            tag: base_fragment_info.tag,
92            flags: base_fragment_info.flags,
93            style,
94            rect: Rect::new(
95                Point2D::new(rect.origin.x.0.into(), rect.origin.y.0.into()),
96                Size2D::new(rect.size.width.0.into(), rect.size.height.0.into()),
97            ),
98            status: AtomicU8::new(FragmentStatus::New as u8),
99        }
100    }
101
102    #[inline]
103    pub(crate) fn rect(&self) -> PhysicalRect<Au> {
104        PhysicalRect::new(
105            Point2D::new(
106                Au::new(self.rect.origin.x.load(Ordering::Relaxed)),
107                Au::new(self.rect.origin.y.load(Ordering::Relaxed)),
108            ),
109            Size2D::new(
110                Au::new(self.rect.size.width.load(Ordering::Relaxed)),
111                Au::new(self.rect.size.height.load(Ordering::Relaxed)),
112            ),
113        )
114    }
115
116    #[inline]
117    pub(crate) fn set_rect(&self, new_rect: PhysicalRect<Au>) {
118        let origin = &self.rect.origin;
119        origin.x.store(new_rect.origin.x.0, Ordering::Relaxed);
120        origin.y.store(new_rect.origin.y.0, Ordering::Relaxed);
121
122        let size = &self.rect.size;
123        size.width.store(new_rect.size.width.0, Ordering::Relaxed);
124        size.height.store(new_rect.size.height.0, Ordering::Relaxed);
125    }
126
127    #[inline]
128    pub(crate) fn translate_rect(&self, offset: PhysicalSize<Au>) {
129        // This code explicitly does not use `AtomicI32::fetch_add`, as we rely on Au's
130        // overflow detection to clamp the resulting value between `MAX_AU` and `MIN_AU`.
131        let origin = &self.rect.origin;
132        let new_x = Au::new(origin.x.load(Ordering::Relaxed)) + offset.width;
133        origin.x.store(new_x.0, Ordering::Relaxed);
134        let new_y = Au::new(origin.y.load(Ordering::Relaxed)) + offset.height;
135        origin.y.store(new_y.0, Ordering::Relaxed);
136    }
137
138    #[inline]
139    pub(crate) fn set_rect_origin(&self, offset: PhysicalPoint<Au>) {
140        let origin = &self.rect.origin;
141        origin.x.store(offset.x.0, Ordering::Relaxed);
142        origin.y.store(offset.y.0, Ordering::Relaxed);
143    }
144
145    pub(crate) fn is_anonymous(&self) -> bool {
146        self.tag.is_none()
147    }
148
149    pub(crate) fn status(&self) -> FragmentStatus {
150        FragmentStatus::from_u8(self.status.load(Ordering::Relaxed))
151            .expect("Unknown FragmentStatus value")
152    }
153
154    pub(crate) fn set_status(&self, new_status: FragmentStatus) {
155        self.status.store(new_status as u8, Ordering::Relaxed)
156    }
157
158    pub(crate) fn repair_style(&self, style: &ServoArc<ComputedValues>) {
159        *self.style.borrow_mut() = style.clone();
160        self.set_status(FragmentStatus::StyleChanged);
161    }
162
163    pub(crate) fn style<'a>(&'a self) -> AtomicRef<'a, ServoArc<ComputedValues>> {
164        self.style.borrow()
165    }
166}
167
168/// Information necessary to construct a new BaseFragment.
169#[derive(Clone, Copy, Debug, MallocSizeOf)]
170pub(crate) struct BaseFragmentInfo {
171    /// The tag to use for the new BaseFragment, if it is not an anonymous Fragment.
172    pub tag: Option<Tag>,
173
174    /// The flags to use for the new BaseFragment.
175    pub flags: FragmentFlags,
176}
177
178impl BaseFragmentInfo {
179    pub(crate) fn anonymous() -> Self {
180        Self {
181            tag: None,
182            flags: FragmentFlags::empty(),
183        }
184    }
185
186    pub(crate) fn new_for_testing(id: usize) -> Self {
187        Self {
188            tag: Some(Tag {
189                node: OpaqueNode(id),
190                pseudo_element_chain: Default::default(),
191            }),
192            flags: FragmentFlags::empty(),
193        }
194    }
195
196    pub(crate) fn is_anonymous(&self) -> bool {
197        self.tag.is_none()
198    }
199}
200
201impl From<&NodeAndStyleInfo<'_>> for BaseFragmentInfo {
202    fn from(info: &NodeAndStyleInfo) -> Self {
203        info.node.into()
204    }
205}
206
207impl From<ServoLayoutNode<'_>> for BaseFragmentInfo {
208    fn from(node: ServoLayoutNode) -> Self {
209        let pseudo_element_chain = node.pseudo_element_chain();
210        let mut flags = FragmentFlags::empty();
211
212        // Anonymous boxes should not have a tag, because they should not take part in hit testing.
213        //
214        // TODO(mrobinson): It seems that anonymous boxes should take part in hit testing in some
215        // cases, but currently this means that the order of hit test results isn't as expected for
216        // some WPT tests. This needs more investigation.
217        if matches!(
218            pseudo_element_chain.innermost(),
219            Some(PseudoElement::ServoAnonymousBox) |
220                Some(PseudoElement::ServoAnonymousTable) |
221                Some(PseudoElement::ServoAnonymousTableCell) |
222                Some(PseudoElement::ServoAnonymousTableRow)
223        ) {
224            return Self::anonymous();
225        }
226
227        if let Some(element) = node.as_html_element() {
228            if element.is_body_element_of_html_element_root() {
229                flags.insert(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT);
230            }
231
232            match element.local_name() {
233                &local_name!("br") => {
234                    flags.insert(FragmentFlags::IS_BR_ELEMENT);
235                },
236                &local_name!("table") | &local_name!("th") | &local_name!("td") => {
237                    flags.insert(FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT);
238                },
239                &local_name!("input") => {
240                    flags.insert(FragmentFlags::IS_INPUT_ELEMENT);
241                },
242                _ => {},
243            }
244
245            if element.is_root() {
246                flags.insert(FragmentFlags::IS_ROOT_ELEMENT);
247            }
248        };
249
250        Self {
251            tag: Some(node.into()),
252            flags,
253        }
254    }
255}
256
257bitflags! {
258    /// Flags used to track various information about a DOM node during layout.
259    #[derive(Clone, Copy, Debug)]
260    pub(crate) struct FragmentFlags: u16 {
261        /// Whether or not the node that created this fragment is a `<body>` element on an HTML document.
262        const IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT = 1 << 0;
263        /// Whether or not the node that created this Fragment is a `<br>` element.
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
300    }
301}
302
303malloc_size_of_is_0!(FragmentFlags);
304
305/// A data structure used to hold DOM and pseudo-element information about
306/// a particular layout object.
307#[derive(Clone, Copy, Eq, MallocSizeOf, PartialEq)]
308pub(crate) struct Tag {
309    pub(crate) node: OpaqueNode,
310    pub(crate) pseudo_element_chain: PseudoElementChain,
311}
312
313impl std::fmt::Debug for Tag {
314    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315        f.write_fmt(format_args!("Tag({:?}", self.node))?;
316        if let Some(pseudo) = self.pseudo_element_chain.primary {
317            f.write_fmt(format_args!(", PseudoElement::{pseudo:?}"))?;
318        }
319        if let Some(pseudo) = self.pseudo_element_chain.secondary {
320            f.write_fmt(format_args!(", PseudoElement::{pseudo:?}"))?;
321        }
322        f.write_str(")")
323    }
324}
325
326impl Tag {
327    pub(crate) fn to_display_list_fragment_id(self) -> u64 {
328        combine_id_with_fragment_type(self.node.id(), self.pseudo_element_chain.primary.into())
329    }
330}
331
332impl From<ServoLayoutNode<'_>> for Tag {
333    fn from(node: ServoLayoutNode<'_>) -> Self {
334        Self {
335            node: node.opaque(),
336            pseudo_element_chain: node.pseudo_element_chain(),
337        }
338    }
339}