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