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, AtomicRefMut};
9use base::id::PipelineId;
10use base::print_tree::PrintTree;
11use euclid::{Point2D, Rect, Size2D};
12use fonts::{FontMetrics, GlyphStore};
13use layout_api::BoxAreaType;
14use malloc_size_of_derive::MallocSizeOf;
15use style::Zero;
16use style_traits::CSSPixel;
17use webrender_api::{FontInstanceKey, ImageKey};
18
19use super::{
20    BaseFragment, BoxFragment, ContainingBlockManager, HoistedSharedFragment, PositioningFragment,
21    Tag,
22};
23use crate::SharedStyle;
24use crate::cell::ArcRefCell;
25use crate::flow::inline::line::TextRunOffsets;
26use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect};
27use crate::style_ext::ComputedValuesExt;
28
29#[derive(Clone, MallocSizeOf)]
30pub(crate) enum Fragment {
31    Box(ArcRefCell<BoxFragment>),
32    /// Floating content. A floated fragment is very similar to a normal
33    /// [BoxFragment] but it isn't positioned using normal in block flow
34    /// positioning rules (margin collapse, etc). Instead, they are laid
35    /// out by the [crate::flow::float::SequentialLayoutState] of their
36    /// float containing block formatting context.
37    Float(ArcRefCell<BoxFragment>),
38    Positioning(ArcRefCell<PositioningFragment>),
39    /// Absolute and fixed position fragments are hoisted up so that they
40    /// are children of the BoxFragment that establishes their containing
41    /// blocks, so that they can be laid out properly. When this happens
42    /// an `AbsoluteOrFixedPositioned` fragment is left at the original tree
43    /// position. This allows these hoisted fragments to be painted with
44    /// regard to their original tree order during stacking context tree /
45    /// display list construction.
46    AbsoluteOrFixedPositioned(ArcRefCell<HoistedSharedFragment>),
47    Text(ArcRefCell<TextFragment>),
48    Image(ArcRefCell<ImageFragment>),
49    IFrame(ArcRefCell<IFrameFragment>),
50}
51
52#[derive(Clone, MallocSizeOf)]
53pub(crate) struct CollapsedBlockMargins {
54    pub collapsed_through: bool,
55    pub start: CollapsedMargin,
56    pub end: CollapsedMargin,
57}
58
59#[derive(Clone, Copy, Debug, MallocSizeOf)]
60pub(crate) struct CollapsedMargin {
61    max_positive: Au,
62    min_negative: Au,
63}
64
65#[derive(MallocSizeOf)]
66pub(crate) struct TextFragment {
67    pub base: BaseFragment,
68    pub selected_style: SharedStyle,
69    #[conditional_malloc_size_of]
70    pub font_metrics: Arc<FontMetrics>,
71    pub font_key: FontInstanceKey,
72    #[conditional_malloc_size_of]
73    pub glyphs: Vec<Arc<GlyphStore>>,
74    /// Extra space to add for each justification opportunity.
75    pub justification_adjustment: Au,
76    /// When necessary, this field store the [`TextRunOffsets`] for a particular
77    /// [`TextRunLineItem`]. This is currently only used inside of text inputs.
78    pub offsets: Option<Box<TextRunOffsets>>,
79}
80
81#[derive(MallocSizeOf)]
82pub(crate) struct ImageFragment {
83    pub base: BaseFragment,
84    pub clip: PhysicalRect<Au>,
85    pub image_key: Option<ImageKey>,
86    pub showing_broken_image_icon: bool,
87}
88
89#[derive(MallocSizeOf)]
90pub(crate) struct IFrameFragment {
91    pub base: BaseFragment,
92    pub pipeline_id: PipelineId,
93}
94
95impl Fragment {
96    pub fn base<'a>(&'a self) -> Option<AtomicRef<'a, BaseFragment>> {
97        Some(match self {
98            Fragment::Box(fragment) => AtomicRef::map(fragment.borrow(), |fragment| &fragment.base),
99            Fragment::Text(fragment) => {
100                AtomicRef::map(fragment.borrow(), |fragment| &fragment.base)
101            },
102            Fragment::AbsoluteOrFixedPositioned(_) => return None,
103            Fragment::Positioning(fragment) => {
104                AtomicRef::map(fragment.borrow(), |fragment| &fragment.base)
105            },
106            Fragment::Image(fragment) => {
107                AtomicRef::map(fragment.borrow(), |fragment| &fragment.base)
108            },
109            Fragment::IFrame(fragment) => {
110                AtomicRef::map(fragment.borrow(), |fragment| &fragment.base)
111            },
112            Fragment::Float(fragment) => {
113                AtomicRef::map(fragment.borrow(), |fragment| &fragment.base)
114            },
115        })
116    }
117
118    pub fn base_mut<'a>(&'a self) -> Option<AtomicRefMut<'a, BaseFragment>> {
119        Some(match self {
120            Fragment::Box(fragment) => {
121                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
122            },
123            Fragment::Text(fragment) => {
124                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
125            },
126            Fragment::AbsoluteOrFixedPositioned(_) => return None,
127            Fragment::Positioning(fragment) => {
128                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
129            },
130            Fragment::Image(fragment) => {
131                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
132            },
133            Fragment::IFrame(fragment) => {
134                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
135            },
136            Fragment::Float(fragment) => {
137                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
138            },
139        })
140    }
141
142    pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect<Au>) {
143        match self {
144            Fragment::Box(box_fragment) => box_fragment
145                .borrow_mut()
146                .set_containing_block(containing_block),
147            Fragment::Float(float_fragment) => float_fragment
148                .borrow_mut()
149                .set_containing_block(containing_block),
150            Fragment::Positioning(positioning_fragment) => positioning_fragment
151                .borrow_mut()
152                .set_containing_block(containing_block),
153            Fragment::AbsoluteOrFixedPositioned(hoisted_shared_fragment) => {
154                if let Some(ref fragment) = hoisted_shared_fragment.borrow().fragment {
155                    fragment.set_containing_block(containing_block);
156                }
157            },
158            Fragment::Text(_) => {},
159            Fragment::Image(_) => {},
160            Fragment::IFrame(_) => {},
161        }
162    }
163
164    pub fn tag(&self) -> Option<Tag> {
165        self.base().and_then(|base| base.tag)
166    }
167
168    pub fn print(&self, tree: &mut PrintTree) {
169        match self {
170            Fragment::Box(fragment) => fragment.borrow().print(tree),
171            Fragment::Float(fragment) => {
172                tree.new_level("Float".to_string());
173                fragment.borrow().print(tree);
174                tree.end_level();
175            },
176            Fragment::AbsoluteOrFixedPositioned(_) => {
177                tree.add_item("AbsoluteOrFixedPositioned".to_string());
178            },
179            Fragment::Positioning(fragment) => fragment.borrow().print(tree),
180            Fragment::Text(fragment) => fragment.borrow().print(tree),
181            Fragment::Image(fragment) => fragment.borrow().print(tree),
182            Fragment::IFrame(fragment) => fragment.borrow().print(tree),
183        }
184    }
185
186    pub(crate) fn scrolling_area(&self) -> PhysicalRect<Au> {
187        match self {
188            Fragment::Box(fragment) | Fragment::Float(fragment) => {
189                let fragment = fragment.borrow();
190                fragment.offset_by_containing_block(&fragment.scrollable_overflow())
191            },
192            _ => self.scrollable_overflow_for_parent(),
193        }
194    }
195
196    pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
197        match self {
198            Fragment::Box(fragment) | Fragment::Float(fragment) => {
199                return fragment.borrow().scrollable_overflow_for_parent();
200            },
201            Fragment::Positioning(fragment) => fragment.borrow().scrollable_overflow_for_parent(),
202            Fragment::AbsoluteOrFixedPositioned(_) |
203            Fragment::Text(..) |
204            Fragment::Image(..) |
205            Fragment::IFrame(..) => self.base().map(|base| base.rect).unwrap_or_default(),
206        }
207    }
208
209    pub(crate) fn calculate_scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
210        self.calculate_scrollable_overflow();
211        self.scrollable_overflow_for_parent()
212    }
213
214    pub(crate) fn calculate_scrollable_overflow(&self) {
215        match self {
216            Fragment::Box(fragment) | Fragment::Float(fragment) => {
217                fragment.borrow_mut().calculate_scrollable_overflow()
218            },
219            Fragment::Positioning(fragment) => {
220                fragment.borrow_mut().calculate_scrollable_overflow()
221            },
222            _ => {},
223        }
224    }
225
226    pub(crate) fn cumulative_box_area_rect(&self, area: BoxAreaType) -> Option<PhysicalRect<Au>> {
227        match self {
228            Fragment::Box(fragment) | Fragment::Float(fragment) => Some(match area {
229                BoxAreaType::Content => fragment.borrow().cumulative_content_box_rect(),
230                BoxAreaType::Padding => fragment.borrow().cumulative_padding_box_rect(),
231                BoxAreaType::Border => fragment.borrow().cumulative_border_box_rect(),
232            }),
233            Fragment::Positioning(fragment) => {
234                let fragment = fragment.borrow();
235                Some(fragment.offset_by_containing_block(&fragment.base.rect))
236            },
237            Fragment::Text(_) |
238            Fragment::AbsoluteOrFixedPositioned(_) |
239            Fragment::Image(_) |
240            Fragment::IFrame(_) => None,
241        }
242    }
243
244    pub(crate) fn client_rect(&self) -> Rect<i32, CSSPixel> {
245        let rect = match self {
246            Fragment::Box(fragment) | Fragment::Float(fragment) => {
247                // https://drafts.csswg.org/cssom-view/#dom-element-clienttop
248                // " If the element has no associated CSS layout box or if the
249                //   CSS layout box is inline, return zero." For this check we
250                // also explicitly ignore the list item portion of the display
251                // style.
252                let fragment = fragment.borrow();
253                if fragment.is_inline_box() {
254                    return Rect::zero();
255                }
256
257                if fragment.is_table_wrapper() {
258                    // For tables the border actually belongs to the table grid box,
259                    // so we need to include it in the dimension of the table wrapper box.
260                    let mut rect = fragment.border_rect();
261                    rect.origin = PhysicalPoint::zero();
262                    rect
263                } else {
264                    let mut rect = fragment.padding_rect();
265                    rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top);
266                    rect
267                }
268            },
269            _ => return Rect::zero(),
270        };
271
272        let rect = Rect::new(
273            Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
274            Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
275        );
276        rect.round().to_i32()
277    }
278
279    pub(crate) fn children<'a>(&'a self) -> Option<AtomicRef<'a, Vec<Fragment>>> {
280        match self {
281            Fragment::Box(fragment) | Fragment::Float(fragment) => {
282                let fragment = fragment.borrow();
283                Some(AtomicRef::map(fragment, |fragment| &fragment.children))
284            },
285            Fragment::Positioning(fragment) => {
286                let fragment = fragment.borrow();
287                Some(AtomicRef::map(fragment, |fragment| &fragment.children))
288            },
289            _ => None,
290        }
291    }
292
293    pub(crate) fn find<T>(
294        &self,
295        manager: &ContainingBlockManager<PhysicalRect<Au>>,
296        level: usize,
297        process_func: &mut impl FnMut(&Fragment, usize, &PhysicalRect<Au>) -> Option<T>,
298    ) -> Option<T> {
299        let containing_block = manager.get_containing_block_for_fragment(self);
300        if let Some(result) = process_func(self, level, containing_block) {
301            return Some(result);
302        }
303
304        match self {
305            Fragment::Box(fragment) | Fragment::Float(fragment) => {
306                let fragment = fragment.borrow();
307                let style = fragment.style();
308                let content_rect = fragment
309                    .content_rect()
310                    .translate(containing_block.origin.to_vector());
311                let padding_rect = fragment
312                    .padding_rect()
313                    .translate(containing_block.origin.to_vector());
314                let new_manager = if style
315                    .establishes_containing_block_for_all_descendants(fragment.base.flags)
316                {
317                    manager.new_for_absolute_and_fixed_descendants(&content_rect, &padding_rect)
318                } else if style
319                    .establishes_containing_block_for_absolute_descendants(fragment.base.flags)
320                {
321                    manager.new_for_absolute_descendants(&content_rect, &padding_rect)
322                } else {
323                    manager.new_for_non_absolute_descendants(&content_rect)
324                };
325
326                fragment
327                    .children
328                    .iter()
329                    .find_map(|child| child.find(&new_manager, level + 1, process_func))
330            },
331            Fragment::Positioning(fragment) => {
332                let fragment = fragment.borrow();
333                let content_rect = fragment
334                    .base
335                    .rect
336                    .translate(containing_block.origin.to_vector());
337                let new_manager = manager.new_for_non_absolute_descendants(&content_rect);
338                fragment
339                    .children
340                    .iter()
341                    .find_map(|child| child.find(&new_manager, level + 1, process_func))
342            },
343            _ => None,
344        }
345    }
346
347    pub(crate) fn retrieve_box_fragment(&self) -> Option<&ArcRefCell<BoxFragment>> {
348        match self {
349            Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => Some(box_fragment),
350            _ => None,
351        }
352    }
353}
354
355impl TextFragment {
356    pub fn print(&self, tree: &mut PrintTree) {
357        tree.add_item(format!(
358            "Text num_glyphs={} box={:?}",
359            self.glyphs
360                .iter()
361                .map(|glyph_store| glyph_store.len())
362                .sum::<usize>(),
363            self.base.rect
364        ));
365    }
366
367    /// Find the distance between for point relative to a [`TextFragment`] for the
368    /// purposes of finding a glyph offset. This is used to identify the most relevant
369    /// fragment for glyph offset queries during click handling.
370    pub(crate) fn distance_to_point_for_glyph_offset(
371        &self,
372        point_in_fragment: Point2D<Au, CSSPixel>,
373    ) -> Option<Au> {
374        // Accept any `TextFragment` that is within the vertical range of the point, as one
375        // can click past the end of a line to move the cursor to its end.
376        let rect = &self.base.rect;
377        if point_in_fragment.y < Au::zero() || point_in_fragment.y > rect.height() {
378            return None;
379        }
380        // Only consider clicks that are to the right of the fragment's origin.
381        if point_in_fragment.x < Au::zero() {
382            return None;
383        }
384        Some(point_in_fragment.x - rect.width().max(Au::zero()))
385    }
386
387    /// Given a point relative to this [`TextFragment`], find the most appropriate character
388    /// offset. Note that the given point may be outside the [`TextFragment`]'s content rect.
389    pub(crate) fn character_offset(&self, point_in_fragment: Point2D<Au, CSSPixel>) -> usize {
390        let Some(offsets) = self.offsets.as_ref() else {
391            return 0;
392        };
393
394        let mut current_character = offsets.character_range.start;
395        let mut current_offset = Au::zero();
396        for glyph_store in &self.glyphs {
397            for glyph in glyph_store.glyphs() {
398                let mut advance = glyph.advance();
399                if glyph.char_is_word_separator() {
400                    advance += self.justification_adjustment;
401                }
402                if current_offset + advance.scale_by(0.5) >= point_in_fragment.x {
403                    return current_character;
404                }
405                current_offset += advance;
406                current_character += glyph.character_count();
407            }
408        }
409
410        current_character
411    }
412}
413
414impl ImageFragment {
415    pub fn print(&self, tree: &mut PrintTree) {
416        tree.add_item(format!(
417            "Image\
418                \nrect={:?}",
419            self.base.rect
420        ));
421    }
422}
423
424impl IFrameFragment {
425    pub fn print(&self, tree: &mut PrintTree) {
426        tree.add_item(format!(
427            "IFrame\
428                \npipeline={:?} rect={:?}",
429            self.pipeline_id, self.base.rect
430        ));
431    }
432}
433
434impl CollapsedBlockMargins {
435    pub fn from_margin(margin: &LogicalSides<Au>) -> Self {
436        Self {
437            collapsed_through: false,
438            start: CollapsedMargin::new(margin.block_start),
439            end: CollapsedMargin::new(margin.block_end),
440        }
441    }
442
443    pub fn zero() -> Self {
444        Self {
445            collapsed_through: false,
446            start: CollapsedMargin::zero(),
447            end: CollapsedMargin::zero(),
448        }
449    }
450}
451
452impl CollapsedMargin {
453    pub fn zero() -> Self {
454        Self {
455            max_positive: Au::zero(),
456            min_negative: Au::zero(),
457        }
458    }
459
460    pub fn new(margin: Au) -> Self {
461        Self {
462            max_positive: margin.max(Au::zero()),
463            min_negative: margin.min(Au::zero()),
464        }
465    }
466
467    pub fn adjoin(&self, other: &Self) -> Self {
468        Self {
469            max_positive: self.max_positive.max(other.max_positive),
470            min_negative: self.min_negative.min(other.min_negative),
471        }
472    }
473
474    pub fn adjoin_assign(&mut self, other: &Self) {
475        *self = self.adjoin(other);
476    }
477
478    pub fn solve(&self) -> Au {
479        self.max_positive + self.min_negative
480    }
481}