Skip to main content

layout/fragment_tree/
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::Arc;
6
7use app_units::Au;
8use atomic_refcell::AtomicRef;
9use euclid::{Point2D, Rect, Size2D};
10use fonts::{FontMetrics, ShapedTextSlice};
11use layout_api::BoxAreaType;
12use malloc_size_of_derive::MallocSizeOf;
13use servo_base::id::PipelineId;
14use servo_base::print_tree::PrintTree;
15use servo_url::ServoUrl;
16use style::Zero;
17use style_traits::CSSPixel;
18use webrender_api::{FontInstanceKey, ImageKey};
19
20use super::{
21    BaseFragment, BoxFragment, ContainingBlockManager, HoistedSharedFragment, PositioningFragment,
22    Tag,
23};
24use crate::SharedStyle;
25use crate::cell::{ArcRefCell, RefOrAtomicRef};
26use crate::flow::inline::line::TextRunOffsets;
27use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect};
28use crate::layout_impl::LayoutThread;
29use crate::style_ext::ComputedValuesExt;
30
31#[derive(Clone, MallocSizeOf)]
32pub(crate) enum Fragment {
33    LayoutRoot(LayoutRootFragment),
34    Box(#[conditional_malloc_size_of] Arc<BoxFragment>),
35    /// Floating content. A floated fragment is very similar to a normal
36    /// [BoxFragment] but it isn't positioned using normal in block flow
37    /// positioning rules (margin collapse, etc). Instead, they are laid
38    /// out by the [crate::flow::float::SequentialLayoutState] of their
39    /// float containing block formatting context.
40    Float(#[conditional_malloc_size_of] Arc<BoxFragment>),
41    Positioning(#[conditional_malloc_size_of] Arc<PositioningFragment>),
42    /// Absolute and fixed position fragments are hoisted up so that they are children of the
43    /// BoxFragment that establishes their containing blocks, so that they can be laid out properly.
44    /// When this happens an `AbsoluteOrFixedPositionedPlaceholder` fragment is left at the original
45    /// tree position. This allows these hoisted fragments to be painted with regard to their
46    /// original tree order during stacking context tree / display list construction.
47    AbsoluteOrFixedPositionedPlaceholder(ArcRefCell<HoistedSharedFragment>),
48    Text(#[conditional_malloc_size_of] Arc<TextFragment>),
49    Image(#[conditional_malloc_size_of] Arc<ImageFragment>),
50    IFrame(#[conditional_malloc_size_of] Arc<IFrameFragment>),
51}
52
53#[derive(Clone, MallocSizeOf)]
54pub(crate) struct CollapsedBlockMargins {
55    pub collapsed_through: bool,
56    pub start: CollapsedMargin,
57    pub end: CollapsedMargin,
58}
59
60#[derive(Clone, Copy, Debug, MallocSizeOf)]
61pub(crate) struct CollapsedMargin {
62    max_positive: Au,
63    min_negative: Au,
64}
65
66#[derive(Clone, MallocSizeOf)]
67pub(crate) struct LayoutRootFragment {
68    pub fragment: ArcRefCell<HoistedSharedFragment>,
69}
70
71impl LayoutRootFragment {
72    pub(crate) fn inner(&self) -> AtomicRef<'_, Fragment> {
73        AtomicRef::map(self.fragment.borrow(), |fragment| {
74            fragment
75                .fragment
76                .as_ref()
77                .expect("Should never create LayoutRoot without a Fragment")
78        })
79    }
80
81    pub(crate) fn inner_box_fragment(&self) -> AtomicRef<'_, Arc<BoxFragment>> {
82        AtomicRef::map(self.inner(), |fragment| match fragment {
83            Fragment::Box(box_fragment) => box_fragment,
84            _ => unreachable!("Layout root should always contain box fragment"),
85        })
86    }
87}
88
89#[derive(MallocSizeOf)]
90pub(crate) struct TextFragment {
91    pub base: BaseFragment,
92    pub selected_style: SharedStyle,
93    #[conditional_malloc_size_of]
94    pub font_metrics: Arc<FontMetrics>,
95    pub font_key: FontInstanceKey,
96    #[conditional_malloc_size_of]
97    pub glyphs: Vec<Arc<ShapedTextSlice>>,
98    /// Extra space to add for each justification opportunity.
99    pub justification_adjustment: Au,
100    /// When necessary, this field store the [`TextRunOffsets`] for a particular
101    /// [`TextRunLineItem`]. This is currently only used inside of text inputs.
102    pub offsets: Option<Box<TextRunOffsets>>,
103    /// Whether or not this [`TextFragment`] is an empty fragment added for the
104    /// benefit of placing a text cursor on an otherwise empty editable line.
105    pub is_empty_for_text_cursor: bool,
106}
107
108#[derive(MallocSizeOf)]
109pub(crate) struct ImageFragment {
110    pub base: BaseFragment,
111    pub clip: PhysicalRect<Au>,
112    pub image_key: Option<ImageKey>,
113    pub showing_broken_image_icon: bool,
114    pub url: Option<ServoUrl>,
115}
116
117#[derive(MallocSizeOf)]
118pub(crate) struct IFrameFragment {
119    pub base: BaseFragment,
120    pub pipeline_id: PipelineId,
121}
122
123impl Fragment {
124    pub fn base(&self) -> Option<RefOrAtomicRef<'_, BaseFragment>> {
125        Some(match self {
126            Fragment::LayoutRoot(fragment) => RefOrAtomicRef::AtomicRef(AtomicRef::map(
127                fragment.inner_box_fragment(),
128                |box_fragment| &box_fragment.base,
129            )),
130            Fragment::Box(fragment) => RefOrAtomicRef::Ref(&fragment.base),
131            Fragment::Text(fragment) => RefOrAtomicRef::Ref(&fragment.base),
132            Fragment::AbsoluteOrFixedPositionedPlaceholder(_) => return None,
133            Fragment::Positioning(fragment) => RefOrAtomicRef::Ref(&fragment.base),
134            Fragment::Image(fragment) => RefOrAtomicRef::Ref(&fragment.base),
135            Fragment::IFrame(fragment) => RefOrAtomicRef::Ref(&fragment.base),
136            Fragment::Float(fragment) => RefOrAtomicRef::Ref(&fragment.base),
137        })
138    }
139
140    pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect<Au>) {
141        match self {
142            Fragment::LayoutRoot(layout_root_fragment) => layout_root_fragment
143                .inner()
144                .set_containing_block(containing_block),
145            Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
146                box_fragment.set_containing_block(containing_block)
147            },
148            Fragment::Positioning(positioning_fragment) => {
149                positioning_fragment.set_containing_block(containing_block)
150            },
151            Fragment::AbsoluteOrFixedPositionedPlaceholder(..) |
152            Fragment::Text(..) |
153            Fragment::Image(..) |
154            Fragment::IFrame(..) => {},
155        }
156    }
157
158    pub fn tag(&self) -> Option<Tag> {
159        self.base().and_then(|base| base.tag)
160    }
161
162    pub fn print(&self, tree: &mut PrintTree) {
163        match self {
164            Fragment::LayoutRoot(layout_root_fragment) => layout_root_fragment.inner().print(tree),
165            Fragment::Box(fragment) => fragment.print(tree),
166            Fragment::Float(fragment) => {
167                tree.new_level("Float".to_string());
168                fragment.print(tree);
169                tree.end_level();
170            },
171            Fragment::AbsoluteOrFixedPositionedPlaceholder(_) => {
172                tree.add_item("AbsoluteOrFixedPositioned".to_string());
173            },
174            Fragment::Positioning(fragment) => fragment.print(tree),
175            Fragment::Text(fragment) => fragment.print(tree),
176            Fragment::Image(fragment) => fragment.print(tree),
177            Fragment::IFrame(fragment) => fragment.print(tree),
178        }
179    }
180
181    pub(crate) fn scrolling_area(&self, layout_thread: &LayoutThread) -> PhysicalRect<Au> {
182        self.retrieve_box_fragment().map_or_else(
183            || self.scrollable_overflow_for_parent(),
184            |box_fragment| {
185                box_fragment.offset_by_containing_block(
186                    &box_fragment.with_style().scrollable_overflow(),
187                    layout_thread.into(),
188                )
189            },
190        )
191    }
192
193    /// Clear the scrollable overflow on this [`Fragment`]. This is called during damage
194    /// propagation when a fragment is preserved, itself or one of its descendants has
195    /// scrollable overflow damage.
196    pub(crate) fn clear_scrollable_overflow(&self) {
197        match self {
198            Fragment::LayoutRoot(fragment) => {
199                fragment.inner_box_fragment().clear_scrollable_overflow()
200            },
201            Fragment::Box(fragment) | Fragment::Float(fragment) => {
202                fragment.clear_scrollable_overflow()
203            },
204            Fragment::Positioning(fragment) => fragment.clear_scrollable_overflow(),
205            _ => {},
206        }
207    }
208
209    pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
210        match self {
211            Fragment::LayoutRoot(layout_root) => {
212                layout_root.inner().scrollable_overflow_for_parent()
213            },
214            Fragment::Box(fragment) | Fragment::Float(fragment) => {
215                fragment.with_style().scrollable_overflow_for_parent()
216            },
217            Fragment::Positioning(fragment) => fragment.scrollable_overflow_for_parent(),
218            Fragment::AbsoluteOrFixedPositionedPlaceholder(_) |
219            Fragment::Text(..) |
220            Fragment::Image(..) |
221            Fragment::IFrame(..) => self.base().map(|base| base.rect()).unwrap_or_default(),
222        }
223    }
224
225    /// From <https://drafts.csswg.org/css-overflow-3/#scrollable>:
226    /// > This padding represents, within the scrollable overflow rectangle, the box’s own padding
227    /// > so that when its content is scrolled to its end, there is padding between the edge of its
228    /// > in-flow (or floated) content and the border edge of the box. It typically ends up being
229    /// > exactly the same size as the box’s own padding, except in a few cases—​such as when an
230    /// > out-of-flow positioned element, or the visible overflow of a descendent, has already
231    /// > increased the size of the scrollable overflow rectangle outside the conceptual “content
232    /// > edge” of the scroll container’s content.
233    pub(crate) fn scrollable_overflow_padding_contribution_for_parent(
234        &self,
235    ) -> Option<PhysicalRect<Au>> {
236        match self {
237            // TODO: This should consider the box in pre-relative-adjusted position state.
238            Fragment::Box(fragment) | Fragment::Float(fragment)
239                if !fragment.style().clone_position().is_absolutely_positioned() =>
240            {
241                Some(fragment.margin_rect())
242            },
243            // Layout roots and absolutely positioned elements do not affect scrollable overflow
244            // for parents.
245            Fragment::Box(..) | Fragment::Float(..) | Fragment::LayoutRoot(..) => None,
246            // TODO: This rectangle does not include extra size from overflowing inline items. As
247            // this measurement is concerned with the actual fragments, it's quite likely that this
248            // rectangle should include that.
249            Fragment::Positioning(fragment) => Some(fragment.base.rect()),
250            Fragment::AbsoluteOrFixedPositionedPlaceholder(_) => None,
251            Fragment::Text(..) | Fragment::Image(..) | Fragment::IFrame(..) => {
252                Some(self.base()?.rect())
253            },
254        }
255    }
256
257    pub(crate) fn cumulative_box_area_rect(
258        &self,
259        area: BoxAreaType,
260        containing_block_computation: ContainingBlockCalculation<'_>,
261    ) -> Option<PhysicalRect<Au>> {
262        match self {
263            Fragment::LayoutRoot(layout_root_fragment) => layout_root_fragment
264                .inner()
265                .cumulative_box_area_rect(area, containing_block_computation),
266            Fragment::Box(fragment) | Fragment::Float(fragment) => Some(match area {
267                BoxAreaType::Content => {
268                    fragment.cumulative_content_box_rect(containing_block_computation)
269                },
270                BoxAreaType::Padding => {
271                    fragment.cumulative_padding_box_rect(containing_block_computation)
272                },
273                BoxAreaType::Border => {
274                    fragment.cumulative_border_box_rect(containing_block_computation)
275                },
276            }),
277            Fragment::Positioning(fragment) => {
278                Some(fragment.offset_by_containing_block(
279                    &fragment.base.rect(),
280                    containing_block_computation,
281                ))
282            },
283            Fragment::Text(_) |
284            Fragment::AbsoluteOrFixedPositionedPlaceholder(_) |
285            Fragment::Image(_) |
286            Fragment::IFrame(_) => None,
287        }
288    }
289
290    pub(crate) fn client_rect(&self) -> Rect<i32, CSSPixel> {
291        let Some(fragment) = self.retrieve_box_fragment() else {
292            return Rect::zero();
293        };
294        let fragment = fragment.with_style();
295
296        // https://drafts.csswg.org/cssom-view/#dom-element-clienttop
297        // " If the element has no associated CSS layout box or if the
298        //   CSS layout box is inline, return zero." For this check we
299        // also explicitly ignore the list item portion of the display
300        // style.
301        if fragment.is_inline_box() {
302            return Rect::zero();
303        }
304
305        let rect = if fragment.is_table_wrapper() {
306            // For tables the border actually belongs to the table grid box,
307            // so we need to include it in the dimension of the table wrapper box.
308            let mut rect = fragment.border_rect();
309            rect.origin = PhysicalPoint::zero();
310            rect
311        } else {
312            let mut rect = fragment.padding_rect();
313            rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top);
314            rect
315        };
316
317        let rect = Rect::new(
318            Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
319            Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
320        );
321        rect.round().to_i32()
322    }
323
324    pub(crate) fn children(&self) -> Option<RefOrAtomicRef<'_, Vec<Fragment>>> {
325        match self {
326            Fragment::LayoutRoot(fragment) => Some(RefOrAtomicRef::AtomicRef(AtomicRef::map(
327                fragment.inner_box_fragment(),
328                |fragment| &fragment.children,
329            ))),
330            Fragment::Box(fragment) | Fragment::Float(fragment) => {
331                Some(RefOrAtomicRef::Ref(&fragment.children))
332            },
333            Fragment::Positioning(fragment) => Some(RefOrAtomicRef::Ref(&fragment.children)),
334            _ => None,
335        }
336    }
337
338    pub(crate) fn find<T>(
339        &self,
340        manager: &ContainingBlockManager<PhysicalRect<Au>>,
341        level: usize,
342        process_func: &mut impl FnMut(&Fragment, usize, &PhysicalRect<Au>) -> Option<T>,
343    ) -> Option<T> {
344        let containing_block = manager.get_containing_block_for_fragment(self);
345        if let Some(result) = process_func(self, level, containing_block) {
346            return Some(result);
347        }
348
349        match self {
350            Fragment::LayoutRoot(layout_root_fragment) => {
351                layout_root_fragment
352                    .inner()
353                    .find(manager, level, process_func)
354            },
355            Fragment::Box(fragment) | Fragment::Float(fragment) => {
356                let style = fragment.style();
357                let content_rect = fragment
358                    .content_rect()
359                    .translate(containing_block.origin.to_vector());
360                let padding_rect = fragment
361                    .padding_rect()
362                    .translate(containing_block.origin.to_vector());
363                let new_manager = if style
364                    .establishes_containing_block_for_all_descendants(fragment.base.flags)
365                {
366                    manager.new_for_absolute_and_fixed_descendants(&content_rect, &padding_rect)
367                } else if style
368                    .establishes_containing_block_for_absolute_descendants(fragment.base.flags)
369                {
370                    manager.new_for_absolute_descendants(&content_rect, &padding_rect)
371                } else {
372                    manager.new_for_non_absolute_descendants(&content_rect)
373                };
374
375                fragment
376                    .children
377                    .iter()
378                    .find_map(|child| child.find(&new_manager, level + 1, process_func))
379            },
380            Fragment::Positioning(fragment) => {
381                let content_rect = fragment
382                    .base
383                    .rect()
384                    .translate(containing_block.origin.to_vector());
385                let new_manager = manager.new_for_non_absolute_descendants(&content_rect);
386                fragment
387                    .children
388                    .iter()
389                    .find_map(|child| child.find(&new_manager, level + 1, process_func))
390            },
391            _ => None,
392        }
393    }
394
395    pub(crate) fn retrieve_box_fragment(&self) -> Option<RefOrAtomicRef<'_, Arc<BoxFragment>>> {
396        match self {
397            Fragment::LayoutRoot(layout_root_fragment) => Some(RefOrAtomicRef::AtomicRef(
398                layout_root_fragment.inner_box_fragment(),
399            )),
400            Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
401                Some(RefOrAtomicRef::Ref(box_fragment))
402            },
403            _ => None,
404        }
405    }
406}
407
408impl TextFragment {
409    pub fn print(&self, tree: &mut PrintTree) {
410        tree.add_item(format!(
411            "Text num_glyphs={} box={:?}",
412            self.glyphs
413                .iter()
414                .map(|shaped_text_slice| shaped_text_slice.glyph_count())
415                .sum::<usize>(),
416            self.base.rect()
417        ));
418    }
419
420    /// Whether or not the given point is within the vertical boundaries of this
421    /// [`TextFragment`].
422    pub(crate) fn point_is_within_vertical_boundaries(
423        &self,
424        point_in_fragment: Point2D<Au, CSSPixel>,
425    ) -> bool {
426        let rect = &self.base.rect();
427        rect.min_y() <= point_in_fragment.y && rect.max_y() >= point_in_fragment.y
428    }
429
430    /// Find the distance between for point relative to a [`TextFragment`] for the
431    /// purposes of finding a glyph offset. This is used to identify the most relevant
432    /// fragment for glyph offset queries during click handling.
433    pub(crate) fn distance_to_point_for_glyph_offset(
434        &self,
435        point_in_fragment: Point2D<Au, CSSPixel>,
436    ) -> Au {
437        // This is the distance between the closest point on the edge of the rectangle and
438        // the point. From <https://stackoverflow.com/a/18157551>.
439        let rect = &self.base.rect();
440        let dx = (rect.min_x() - point_in_fragment.x)
441            .max(Au::zero())
442            .max(point_in_fragment.x - rect.max_x());
443        let dy = (rect.min_y() - point_in_fragment.y)
444            .max(Au::zero())
445            .max(point_in_fragment.y - rect.max_y());
446        Au::from_f64_px((dx.to_f64_px().powi(2) + dy.to_f64_px().powi(2)).sqrt())
447    }
448
449    /// Given a point relative to this [`TextFragment`], find the most appropriate
450    /// character offset.
451    ///
452    /// Note that the given point may be outside the [`TextFragment`]'s content rect:
453    ///
454    ///  - If the point is vertically above the [`TextFragment`] the first offset will be returned.
455    ///  - If the point is vertically below the [`TextFragment`], `None` will be returned.
456    pub(crate) fn character_offset(
457        &self,
458        point_in_fragment: Point2D<Au, CSSPixel>,
459    ) -> Option<usize> {
460        // If the click was far enough above the top of the fragment, then pick the first index.
461        let offsets = self.offsets.as_ref()?;
462        let max_vertical_offset = self.base.rect().height().scale_by(0.25);
463        if point_in_fragment.y < -max_vertical_offset {
464            return Some(offsets.character_range.start);
465        }
466
467        // If the click was below the fragment, return `None`, which will cause the
468        // caller to move the cursor to the end.
469        //
470        // TODO: It would be nice to just return the last offset here, but <textarea>
471        // does not currently make a fragment for all selection indices.
472        if point_in_fragment.y > self.base.rect().max_y() + max_vertical_offset {
473            return None;
474        }
475
476        let mut current_character = offsets.character_range.start;
477        let mut current_offset = Au::zero();
478        for glyph_store in &self.glyphs {
479            for glyph in glyph_store.glyphs() {
480                let mut advance = glyph.advance();
481                if glyph.char_is_word_separator() {
482                    advance += self.justification_adjustment;
483                }
484                if current_offset + advance.scale_by(0.5) >= point_in_fragment.x {
485                    return Some(current_character);
486                }
487                current_offset += advance;
488                current_character += glyph.character_count();
489            }
490        }
491
492        Some(current_character)
493    }
494}
495
496impl ImageFragment {
497    pub fn print(&self, tree: &mut PrintTree) {
498        tree.add_item(format!(
499            "Image\
500                \nrect={:?}",
501            self.base.rect()
502        ));
503    }
504}
505
506impl IFrameFragment {
507    pub fn print(&self, tree: &mut PrintTree) {
508        tree.add_item(format!(
509            "IFrame\
510                \npipeline={:?} rect={:?}",
511            self.pipeline_id,
512            self.base.rect()
513        ));
514    }
515}
516
517impl CollapsedBlockMargins {
518    pub fn from_margin(margin: &LogicalSides<Au>) -> Self {
519        Self {
520            collapsed_through: false,
521            start: CollapsedMargin::new(margin.block_start),
522            end: CollapsedMargin::new(margin.block_end),
523        }
524    }
525
526    pub fn zero() -> Self {
527        Self {
528            collapsed_through: false,
529            start: CollapsedMargin::zero(),
530            end: CollapsedMargin::zero(),
531        }
532    }
533}
534
535impl CollapsedMargin {
536    pub fn zero() -> Self {
537        Self {
538            max_positive: Au::zero(),
539            min_negative: Au::zero(),
540        }
541    }
542
543    pub fn new(margin: Au) -> Self {
544        Self {
545            max_positive: margin.max(Au::zero()),
546            min_negative: margin.min(Au::zero()),
547        }
548    }
549
550    pub fn adjoin(&self, other: &Self) -> Self {
551        Self {
552            max_positive: self.max_positive.max(other.max_positive),
553            min_negative: self.min_negative.min(other.min_negative),
554        }
555    }
556
557    pub fn adjoin_assign(&mut self, other: &Self) {
558        *self = self.adjoin(other);
559    }
560
561    pub fn solve(&self) -> Au {
562        self.max_positive + self.min_negative
563    }
564}
565
566/// A token which ensures the calculation and assignment of cumulative containing
567/// blocks to fragments in the fragment tree. This is used because these cumulative
568/// containing block offsets are set during stacking context tree construction, but
569/// some queries might need them beforehand. If the query is executed before stacking
570/// context tree construction, a quick traversal is performed to calculate them for
571/// the purpose of the query.
572pub(crate) enum ContainingBlockCalculation<'a> {
573    /// This token variant is for the purpose of a layout query. In this case, if stacking
574    /// context tree construction has not yet taken place, a cumulative containing block
575    /// calculation traversal will be performed.
576    Lazy { layout_thread: &'a LayoutThread },
577    /// This token variant is used when the code can guarantee that stacking context
578    /// tree construction has already taken place.
579    ///
580    /// Note: Using this before stacking context tree construction can lead
581    /// to incorrect layout or layout query results!
582    AlreadyDoneWithStackingContextTree,
583}
584
585impl ContainingBlockCalculation<'_> {
586    pub(crate) fn ensure(&self) {
587        match self {
588            Self::Lazy { layout_thread } => layout_thread.ensure_containing_block_calculation(),
589            Self::AlreadyDoneWithStackingContextTree => {},
590        }
591    }
592}
593
594impl<'a> From<&'a LayoutThread> for ContainingBlockCalculation<'a> {
595    fn from(layout_thread: &'a LayoutThread) -> Self {
596        Self::Lazy { layout_thread }
597    }
598}