layout/display_list/
stacking_context.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 core::f32;
6use std::cell::{Cell, RefCell};
7use std::mem;
8use std::sync::Arc;
9
10use app_units::Au;
11use embedder_traits::ViewportDetails;
12use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
13use log::warn;
14use malloc_size_of_derive::MallocSizeOf;
15use paint_api::display_list::{
16    AxesScrollSensitivity, PaintDisplayListInfo, ReferenceFrameNodeInfo, ScrollableNodeInfo,
17    SpatialTreeNodeInfo, StickyNodeInfo,
18};
19use servo_base::id::ScrollTreeNodeId;
20use servo_base::print_tree::PrintTree;
21use servo_config::opts::DiagnosticsLogging;
22use style::Zero;
23use style::color::{AbsoluteColor, ColorSpace};
24use style::computed_values::float::T as ComputedFloat;
25use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
26use style::computed_values::overflow_x::T as ComputedOverflow;
27use style::computed_values::position::T as ComputedPosition;
28use style::computed_values::text_decoration_style::T as TextDecorationStyle;
29use style::values::computed::angle::Angle;
30use style::values::computed::basic_shape::ClipPath;
31use style::values::computed::{ClipRectOrAuto, Length, TextDecorationLine};
32use style::values::generics::box_::{OverflowClipMarginBox, Perspective};
33use style::values::generics::transform::{self, GenericRotate, GenericScale, GenericTranslate};
34use style::values::specified::TransformStyle;
35use style::values::specified::box_::DisplayOutside;
36use style_traits::CSSPixel;
37use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D};
38use webrender_api::{self as wr, BorderRadius};
39use wr::StickyOffsetBounds;
40use wr::units::{LayoutPixel, LayoutSize};
41
42use super::ClipId;
43use super::clip::StackingContextTreeClipStore;
44use crate::ArcRefCell;
45use crate::display_list::conversions::{FilterToWebRender, ToWebRender};
46use crate::display_list::{BuilderForBoxFragment, DisplayListBuilder, offset_radii};
47use crate::fragment_tree::{
48    BoxFragment, ContainingBlockManager, Fragment, FragmentFlags, FragmentTree,
49    PositioningFragment, SpecificLayoutInfo,
50};
51use crate::geom::{AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides};
52use crate::style_ext::{ComputedValuesExt, TransformExt};
53
54#[derive(Clone)]
55pub(crate) struct ContainingBlock {
56    /// The SpatialId of the spatial node that contains the children
57    /// of this containing block.
58    scroll_node_id: ScrollTreeNodeId,
59
60    /// The size of the parent scroll frame of this containing block, used for resolving
61    /// sticky margins. If this is None, then this is a direct descendant of a reference
62    /// frame and sticky positioning isn't taken into account.
63    scroll_frame_size: Option<LayoutSize>,
64
65    /// The [`ClipId`] to use for the children of this containing block.
66    clip_id: ClipId,
67
68    /// The physical rect of this containing block.
69    rect: PhysicalRect<Au>,
70}
71
72impl ContainingBlock {
73    pub(crate) fn new(
74        rect: PhysicalRect<Au>,
75        scroll_node_id: ScrollTreeNodeId,
76        scroll_frame_size: Option<LayoutSize>,
77        clip_id: ClipId,
78    ) -> Self {
79        ContainingBlock {
80            scroll_node_id,
81            scroll_frame_size,
82            clip_id,
83            rect,
84        }
85    }
86
87    pub(crate) fn new_replacing_rect(&self, rect: &PhysicalRect<Au>) -> Self {
88        ContainingBlock {
89            rect: *rect,
90            ..*self
91        }
92    }
93}
94
95pub(crate) type ContainingBlockInfo<'a> = ContainingBlockManager<'a, ContainingBlock>;
96
97#[derive(Clone, Copy, Debug, Eq, Ord, MallocSizeOf, PartialEq, PartialOrd)]
98pub(crate) enum StackingContextSection {
99    OwnBackgroundsAndBorders,
100    DescendantBackgroundsAndBorders,
101    Foreground,
102    Outline,
103}
104
105#[derive(MallocSizeOf)]
106pub(crate) struct StackingContextTree {
107    /// The root stacking context of this [`StackingContextTree`].
108    pub root_stacking_context: StackingContext,
109
110    /// The information about the WebRender display list that `Paint`
111    /// consumes. This curerntly contains the out-of-band hit testing information
112    /// data structure that `Paint` uses to map hit tests to information
113    /// about the item hit.
114    pub paint_info: PaintDisplayListInfo,
115
116    /// All of the clips collected for this [`StackingContextTree`]. These are added
117    /// for things like `overflow`. More clips may be created later during WebRender
118    /// display list construction, but they are never added here.
119    pub clip_store: StackingContextTreeClipStore,
120}
121
122impl StackingContextTree {
123    /// Create a new [DisplayList] given the dimensions of the layout and the WebRender
124    /// pipeline id.
125    pub fn new(
126        fragment_tree: &FragmentTree,
127        viewport_details: ViewportDetails,
128        pipeline_id: wr::PipelineId,
129        first_reflow: bool,
130        debug: &DiagnosticsLogging,
131    ) -> Self {
132        let scrollable_overflow = fragment_tree.scrollable_overflow();
133        let scrollable_overflow = LayoutSize::from_untyped(Size2D::new(
134            scrollable_overflow.size.width.to_f32_px(),
135            scrollable_overflow.size.height.to_f32_px(),
136        ));
137
138        let viewport_size = viewport_details.layout_size();
139        let paint_info = PaintDisplayListInfo::new(
140            viewport_details,
141            scrollable_overflow,
142            pipeline_id,
143            // This epoch is set when the WebRender display list is built. For now use a dummy value.
144            Default::default(),
145            fragment_tree.viewport_scroll_sensitivity,
146            first_reflow,
147        );
148
149        let root_scroll_node_id = paint_info.root_scroll_node_id;
150        let cb_for_non_fixed_descendants = ContainingBlock::new(
151            fragment_tree.initial_containing_block,
152            root_scroll_node_id,
153            Some(viewport_size),
154            ClipId::INVALID,
155        );
156        let cb_for_fixed_descendants = ContainingBlock::new(
157            fragment_tree.initial_containing_block,
158            paint_info.root_reference_frame_id,
159            None,
160            ClipId::INVALID,
161        );
162
163        // We need to specify all three containing blocks here, because absolute
164        // descdendants of the root cannot share the containing block we specify
165        // for fixed descendants. In this case, they need to have the spatial
166        // id of the root scroll frame, whereas fixed descendants need the
167        // spatial id of the root reference frame so that they do not scroll with
168        // page content.
169        let containing_block_info = ContainingBlockInfo {
170            for_non_absolute_descendants: &cb_for_non_fixed_descendants,
171            for_absolute_descendants: Some(&cb_for_non_fixed_descendants),
172            for_absolute_and_fixed_descendants: &cb_for_fixed_descendants,
173        };
174
175        let mut stacking_context_tree = Self {
176            // This is just a temporary value that will be replaced once we have finished building the tree.
177            root_stacking_context: StackingContext::create_root(root_scroll_node_id, debug),
178            paint_info,
179            clip_store: Default::default(),
180        };
181
182        let mut root_stacking_context = StackingContext::create_root(root_scroll_node_id, debug);
183        let text_decorations = Default::default();
184        for fragment in &fragment_tree.root_fragments {
185            fragment.build_stacking_context_tree(
186                &mut stacking_context_tree,
187                &containing_block_info,
188                &mut root_stacking_context,
189                StackingContextBuildMode::SkipHoisted,
190                &text_decorations,
191            );
192        }
193        root_stacking_context.sort();
194
195        if debug.stacking_context_tree {
196            root_stacking_context.debug_print();
197        }
198
199        stacking_context_tree.root_stacking_context = root_stacking_context;
200
201        stacking_context_tree
202    }
203
204    fn push_reference_frame(
205        &mut self,
206        origin: LayoutPoint,
207        frame_origin_for_query: LayoutPoint,
208        parent_scroll_node_id: ScrollTreeNodeId,
209        transform_style: wr::TransformStyle,
210        transform: LayoutTransform,
211        kind: wr::ReferenceFrameKind,
212    ) -> ScrollTreeNodeId {
213        self.paint_info.scroll_tree.add_scroll_tree_node(
214            Some(parent_scroll_node_id),
215            SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo {
216                origin,
217                frame_origin_for_query,
218                transform_style,
219                transform: transform.into(),
220                kind,
221            }),
222        )
223    }
224
225    fn define_scroll_frame(
226        &mut self,
227        parent_scroll_node_id: ScrollTreeNodeId,
228        external_id: wr::ExternalScrollId,
229        content_rect: LayoutRect,
230        clip_rect: LayoutRect,
231        scroll_sensitivity: AxesScrollSensitivity,
232    ) -> ScrollTreeNodeId {
233        self.paint_info.scroll_tree.add_scroll_tree_node(
234            Some(parent_scroll_node_id),
235            SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo {
236                external_id,
237                content_rect,
238                clip_rect,
239                scroll_sensitivity,
240                offset: LayoutVector2D::zero(),
241                offset_changed: Cell::new(false),
242            }),
243        )
244    }
245
246    fn define_sticky_frame(
247        &mut self,
248        parent_scroll_node_id: ScrollTreeNodeId,
249        frame_rect: LayoutRect,
250        margins: SideOffsets2D<Option<f32>, LayoutPixel>,
251        vertical_offset_bounds: StickyOffsetBounds,
252        horizontal_offset_bounds: StickyOffsetBounds,
253    ) -> ScrollTreeNodeId {
254        self.paint_info.scroll_tree.add_scroll_tree_node(
255            Some(parent_scroll_node_id),
256            SpatialTreeNodeInfo::Sticky(StickyNodeInfo {
257                frame_rect,
258                margins,
259                vertical_offset_bounds,
260                horizontal_offset_bounds,
261            }),
262        )
263    }
264
265    /// Given a [`Fragment`] and a point in the viewport of the page, return the point in
266    /// the [`Fragment`]'s content rectangle in its transformed coordinate system
267    /// (untransformed CSS pixels). Note that the point may be outside the [`Fragment`]'s
268    /// boundaries.
269    ///
270    /// TODO: Currently, this only works for [`BoxFragment`], but we should extend it to
271    /// other types of [`Fragment`]s in the future.
272    pub(crate) fn offset_in_fragment(
273        &self,
274        fragment: &Fragment,
275        point_in_viewport: PhysicalPoint<Au>,
276    ) -> Option<Point2D<Au, CSSPixel>> {
277        let Fragment::Box(fragment) = fragment else {
278            return None;
279        };
280
281        let fragment = fragment.borrow();
282        let spatial_tree_node = fragment.spatial_tree_node()?;
283        let transform = self
284            .paint_info
285            .scroll_tree
286            .cumulative_root_to_node_transform(spatial_tree_node)?;
287        let transformed_point = transform
288            .project_point2d(point_in_viewport.map(Au::to_f32_px).cast_unit())?
289            .map(Au::from_f32_px)
290            .cast_unit();
291
292        // Find the origin of the fragment relative to its reference frame in the same coordinate system.
293        let reference_frame_origin = self
294            .paint_info
295            .scroll_tree
296            .reference_frame_offset(spatial_tree_node)
297            .map(Au::from_f32_px);
298        let fragment_origin =
299            fragment.cumulative_content_box_rect().origin - reference_frame_origin.cast_unit();
300
301        // Use that to find the offset from the fragment origin.
302        Some(transformed_point - fragment_origin)
303    }
304}
305
306/// The text decorations for a Fragment, collecting during [`StackingContextTree`] construction.
307#[derive(Clone, Debug, MallocSizeOf)]
308pub(crate) struct FragmentTextDecoration {
309    pub line: TextDecorationLine,
310    pub color: AbsoluteColor,
311    pub style: TextDecorationStyle,
312}
313
314/// A piece of content that directly belongs to a section of a stacking context.
315///
316/// This is generally part of a fragment, like its borders or foreground, but it
317/// can also be a stacking container that needs to be painted in fragment order.
318#[derive(MallocSizeOf)]
319pub(crate) enum StackingContextContent {
320    /// A fragment that does not generate a stacking context or stacking container.
321    Fragment {
322        scroll_node_id: ScrollTreeNodeId,
323        reference_frame_scroll_node_id: ScrollTreeNodeId,
324        clip_id: ClipId,
325        section: StackingContextSection,
326        containing_block: PhysicalRect<Au>,
327        fragment: Fragment,
328        is_hit_test_for_scrollable_overflow: bool,
329        is_collapsed_table_borders: bool,
330        #[conditional_malloc_size_of]
331        text_decorations: Arc<Vec<FragmentTextDecoration>>,
332    },
333
334    /// An index into [StackingContext::atomic_inline_stacking_containers].
335    ///
336    /// There is no section field, because these are always in [StackingContextSection::Foreground].
337    AtomicInlineStackingContainer { index: usize },
338}
339
340impl StackingContextContent {
341    pub(crate) fn section(&self) -> StackingContextSection {
342        match self {
343            Self::Fragment { section, .. } => *section,
344            Self::AtomicInlineStackingContainer { .. } => StackingContextSection::Foreground,
345        }
346    }
347
348    fn build_display_list_with_section_override(
349        &self,
350        builder: &mut DisplayListBuilder,
351        inline_stacking_containers: &[StackingContext],
352        section_override: Option<StackingContextSection>,
353    ) {
354        match self {
355            Self::Fragment {
356                scroll_node_id,
357                reference_frame_scroll_node_id,
358                clip_id,
359                section,
360                containing_block,
361                fragment,
362                is_hit_test_for_scrollable_overflow,
363                is_collapsed_table_borders,
364                text_decorations,
365            } => {
366                builder.current_scroll_node_id = *scroll_node_id;
367                builder.current_reference_frame_scroll_node_id = *reference_frame_scroll_node_id;
368                builder.current_clip_id = *clip_id;
369                fragment.build_display_list(
370                    builder,
371                    containing_block,
372                    section_override.unwrap_or(*section),
373                    *is_hit_test_for_scrollable_overflow,
374                    *is_collapsed_table_borders,
375                    text_decorations,
376                );
377            },
378            Self::AtomicInlineStackingContainer { index } => {
379                inline_stacking_containers[*index].build_display_list(builder);
380            },
381        }
382    }
383
384    fn build_display_list(
385        &self,
386        builder: &mut DisplayListBuilder,
387        inline_stacking_containers: &[StackingContext],
388    ) {
389        self.build_display_list_with_section_override(builder, inline_stacking_containers, None);
390    }
391
392    fn has_outline(&self) -> bool {
393        match self {
394            StackingContextContent::Fragment { fragment, .. } => match fragment {
395                Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
396                    let box_fragment = box_fragment.borrow();
397                    let style = box_fragment.style();
398                    let outline = style.get_outline();
399                    !outline.outline_style.none_or_hidden() && !outline.outline_width.0.is_zero()
400                },
401                _ => false,
402            },
403            StackingContextContent::AtomicInlineStackingContainer { .. } => false,
404        }
405    }
406}
407
408#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
409pub(crate) enum StackingContextType {
410    RealStackingContext,
411    PositionedStackingContainer,
412    FloatStackingContainer,
413    AtomicInlineStackingContainer,
414}
415
416/// Either a stacking context or a stacking container, per the definitions in
417/// <https://drafts.csswg.org/css-position-4/#painting-order>.
418///
419/// We use the term “real stacking context” in situations that call for a
420/// stacking context but not a stacking container.
421#[derive(MallocSizeOf)]
422pub struct StackingContext {
423    /// The spatial id of this fragment. This is used to properly handle
424    /// things like preserve-3d.
425    scroll_tree_node_id: ScrollTreeNodeId,
426
427    /// The clip chain id of this stacking context if it has one. Used for filter clipping.
428    clip_id: Option<ClipId>,
429
430    /// The [`BoxFragment`] that established this stacking context. We store the fragment here
431    /// rather than just the style, so that incremental layout can automatically update the style.
432    initializing_fragment: Option<ArcRefCell<BoxFragment>>,
433
434    /// The type of this stacking context. Used for collecting and sorting.
435    context_type: StackingContextType,
436
437    /// The contents that need to be painted in fragment order.
438    pub(super) contents: Vec<StackingContextContent>,
439
440    /// Stacking contexts that need to be stolen by the parent stacking context
441    /// if this is a stacking container, that is, real stacking contexts and
442    /// positioned stacking containers (where ‘z-index’ is auto).
443    /// <https://drafts.csswg.org/css-position-4/#paint-a-stacking-container>
444    /// > To paint a stacking container, given a box root and a canvas canvas:
445    /// >  1. Paint a stacking context given root and canvas, treating root as
446    /// >     if it created a new stacking context, but omitting any positioned
447    /// >     descendants or descendants that actually create a stacking context
448    /// >     (letting the parent stacking context paint them, instead).
449    pub(super) real_stacking_contexts_and_positioned_stacking_containers: Vec<StackingContext>,
450
451    /// Float stacking containers.
452    /// Separate from real_stacking_contexts_or_positioned_stacking_containers
453    /// because they should never be stolen by the parent stacking context.
454    /// <https://drafts.csswg.org/css-position-4/#paint-a-stacking-container>
455    pub(super) float_stacking_containers: Vec<StackingContext>,
456
457    /// Atomic inline stacking containers.
458    /// Separate from real_stacking_contexts_or_positioned_stacking_containers
459    /// because they should never be stolen by the parent stacking context, and
460    /// separate from float_stacking_containers so that [StackingContextContent]
461    /// can index into this vec to paint them in fragment order.
462    /// <https://drafts.csswg.org/css-position-4/#paint-a-stacking-container>
463    /// <https://drafts.csswg.org/css-position-4/#paint-a-box-in-a-line-box>
464    pub(super) atomic_inline_stacking_containers: Vec<StackingContext>,
465
466    /// Information gathered about the painting order, for [Self::debug_print].
467    debug_print_items: Option<RefCell<Vec<DebugPrintItem>>>,
468}
469
470/// Refers to one of the child contents or stacking contexts of a [StackingContext].
471#[derive(Clone, Copy, MallocSizeOf)]
472pub struct DebugPrintItem {
473    field: DebugPrintField,
474    index: usize,
475}
476
477/// Refers to one of the vecs of a [StackingContext].
478#[derive(Clone, Copy, MallocSizeOf)]
479pub enum DebugPrintField {
480    Contents,
481    RealStackingContextsAndPositionedStackingContainers,
482    FloatStackingContainers,
483}
484
485impl StackingContext {
486    fn create_descendant(
487        &self,
488        spatial_id: ScrollTreeNodeId,
489        clip_id: ClipId,
490        initializing_fragment: ArcRefCell<BoxFragment>,
491        context_type: StackingContextType,
492    ) -> Self {
493        // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be
494        // used for primitives, but `None` is used for stacking contexts and clip chains. We convert
495        // to the `Option<ClipId>` representation here. Just passing Some(ClipChainId::INVALID)
496        // leads to a crash.
497        let clip_id = match clip_id {
498            ClipId::INVALID => None,
499            clip_id => Some(clip_id),
500        };
501        Self {
502            scroll_tree_node_id: spatial_id,
503            clip_id,
504            initializing_fragment: Some(initializing_fragment),
505            context_type,
506            contents: vec![],
507            real_stacking_contexts_and_positioned_stacking_containers: vec![],
508            float_stacking_containers: vec![],
509            atomic_inline_stacking_containers: vec![],
510            debug_print_items: self.debug_print_items.is_some().then(|| vec![].into()),
511        }
512    }
513
514    fn create_root(root_scroll_node_id: ScrollTreeNodeId, debug: &DiagnosticsLogging) -> Self {
515        Self {
516            scroll_tree_node_id: root_scroll_node_id,
517            clip_id: None,
518            initializing_fragment: None,
519            context_type: StackingContextType::RealStackingContext,
520            contents: vec![],
521            real_stacking_contexts_and_positioned_stacking_containers: vec![],
522            float_stacking_containers: vec![],
523            atomic_inline_stacking_containers: vec![],
524            debug_print_items: debug.stacking_context_tree.then(|| vec![].into()),
525        }
526    }
527
528    /// Add a child stacking context to this stacking context.
529    fn add_stacking_context(&mut self, stacking_context: StackingContext) {
530        match stacking_context.context_type {
531            StackingContextType::RealStackingContext => {
532                &mut self.real_stacking_contexts_and_positioned_stacking_containers
533            },
534            StackingContextType::PositionedStackingContainer => {
535                &mut self.real_stacking_contexts_and_positioned_stacking_containers
536            },
537            StackingContextType::FloatStackingContainer => &mut self.float_stacking_containers,
538            StackingContextType::AtomicInlineStackingContainer => {
539                &mut self.atomic_inline_stacking_containers
540            },
541        }
542        .push(stacking_context)
543    }
544
545    pub(crate) fn z_index(&self) -> i32 {
546        self.initializing_fragment.as_ref().map_or(0, |fragment| {
547            let fragment = fragment.borrow();
548            fragment.style().effective_z_index(fragment.base.flags)
549        })
550    }
551
552    pub(crate) fn sort(&mut self) {
553        self.contents.sort_by_key(|a| a.section());
554        self.real_stacking_contexts_and_positioned_stacking_containers
555            .sort_by_key(|a| a.z_index());
556
557        debug_assert!(
558            self.real_stacking_contexts_and_positioned_stacking_containers
559                .iter()
560                .all(|c| matches!(
561                    c.context_type,
562                    StackingContextType::RealStackingContext |
563                        StackingContextType::PositionedStackingContainer
564                ))
565        );
566        debug_assert!(
567            self.float_stacking_containers
568                .iter()
569                .all(
570                    |c| c.context_type == StackingContextType::FloatStackingContainer &&
571                        c.z_index() == 0
572                )
573        );
574        debug_assert!(
575            self.atomic_inline_stacking_containers
576                .iter()
577                .all(
578                    |c| c.context_type == StackingContextType::AtomicInlineStackingContainer &&
579                        c.z_index() == 0
580                )
581        );
582    }
583
584    fn push_webrender_stacking_context_if_necessary(
585        &self,
586        builder: &mut DisplayListBuilder,
587    ) -> bool {
588        let fragment = match self.initializing_fragment.as_ref() {
589            Some(fragment) => fragment.borrow(),
590            None => return false,
591        };
592
593        // WebRender only uses the stacking context to apply certain effects. If we don't
594        // actually need to create a stacking context, just avoid creating one.
595        let style = fragment.style();
596        let effects = style.get_effects();
597        let transform_style = style.get_used_transform_style();
598        if effects.filter.0.is_empty() &&
599            effects.opacity == 1.0 &&
600            effects.mix_blend_mode == ComputedMixBlendMode::Normal &&
601            !style.has_effective_transform_or_perspective(FragmentFlags::empty()) &&
602            style.clone_clip_path() == ClipPath::None &&
603            transform_style == TransformStyle::Flat
604        {
605            return false;
606        }
607
608        // Create the filter pipeline.
609        let current_color = style.clone_color();
610        let mut filters: Vec<wr::FilterOp> = effects
611            .filter
612            .0
613            .iter()
614            .map(|filter| FilterToWebRender::to_webrender(filter, &current_color))
615            .collect();
616        if effects.opacity != 1.0 {
617            filters.push(wr::FilterOp::Opacity(
618                effects.opacity.into(),
619                effects.opacity,
620            ));
621        }
622
623        // TODO(jdm): WebRender now requires us to create stacking context items
624        //            with the IS_BLEND_CONTAINER flag enabled if any children
625        //            of the stacking context have a blend mode applied.
626        //            This will require additional tracking during layout
627        //            before we start collecting stacking contexts so that
628        //            information will be available when we reach this point.
629        let spatial_id = builder.spatial_id(self.scroll_tree_node_id);
630        let clip_chain_id = self.clip_id.map(|clip_id| builder.clip_chain_id(clip_id));
631        builder.wr().push_stacking_context(
632            LayoutPoint::zero(), // origin
633            spatial_id,
634            style.get_webrender_primitive_flags(),
635            clip_chain_id,
636            transform_style.to_webrender(),
637            effects.mix_blend_mode.to_webrender(),
638            &filters,
639            &[], // filter_datas
640            &[], // filter_primitives
641            wr::RasterSpace::Screen,
642            wr::StackingContextFlags::empty(),
643            None, // snapshot
644        );
645
646        true
647    }
648
649    /// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds>
650    ///
651    /// This is only called for the root `StackingContext`
652    pub(crate) fn build_canvas_background_display_list(
653        &self,
654        builder: &mut DisplayListBuilder,
655        fragment_tree: &crate::fragment_tree::FragmentTree,
656    ) {
657        let Some(root_fragment) = fragment_tree.root_fragments.iter().find(|fragment| {
658            fragment
659                .base()
660                .is_some_and(|base| base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT))
661        }) else {
662            return;
663        };
664        let root_fragment = match root_fragment {
665            Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
666            _ => return,
667        }
668        .borrow();
669
670        let source_style = {
671            // > For documents whose root element is an HTML HTML element or an XHTML html element
672            // > [HTML]: if the computed value of background-image on the root element is none and its
673            // > background-color is transparent, user agents must instead propagate the computed
674            // > values of the background properties from that element’s first HTML BODY or XHTML body
675            // > child element.
676            let root_fragment_style = root_fragment.style();
677            if root_fragment_style.background_is_transparent() {
678                let body_fragment = fragment_tree.body_fragment();
679                builder.paint_body_background = body_fragment.is_none();
680                body_fragment
681                    .map(|body_fragment| body_fragment.borrow().style().clone())
682                    .unwrap_or(root_fragment.style().clone())
683            } else {
684                root_fragment_style.clone()
685            }
686        };
687
688        // This can happen if the root fragment does not have a `<body>` child (either because it is
689        // `display: none` or `display: contents`) or if the `<body>`'s background is transparent.
690        if source_style.background_is_transparent() {
691            return;
692        }
693
694        // The painting area is theoretically the infinite 2D plane,
695        // but we need a rectangle with finite coordinates.
696        //
697        // If the document is smaller than the viewport (and doesn’t scroll),
698        // we still want to paint the rest of the viewport.
699        // If it’s larger, we also want to paint areas reachable after scrolling.
700        let painting_area = fragment_tree
701            .initial_containing_block
702            .union(&fragment_tree.scrollable_overflow())
703            .to_webrender();
704
705        let background_color =
706            source_style.resolve_color(&source_style.get_background().background_color);
707        if background_color.alpha > 0.0 {
708            let common = builder.common_properties(painting_area, &source_style);
709            let color = super::rgba(background_color);
710            builder.wr().push_rect(&common, painting_area, color);
711
712            // From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
713            // First paint ... includes non-default background paint and the enclosing box of an iframe.
714            // The spec is vague. See also: https://github.com/w3c/paint-timing/issues/122
715            let default_background_color = servo_config::pref!(shell_background_color_rgba);
716            let default_background_color = AbsoluteColor::new(
717                ColorSpace::Srgb,
718                default_background_color[0] as f32,
719                default_background_color[1] as f32,
720                default_background_color[2] as f32,
721                default_background_color[3] as f32,
722            )
723            .into_srgb_legacy();
724            if background_color != default_background_color {
725                builder.mark_is_paintable();
726            }
727        }
728
729        let mut fragment_builder = BuilderForBoxFragment::new(
730            &root_fragment,
731            &fragment_tree.initial_containing_block,
732            false, /* is_hit_test_for_scrollable_overflow */
733            false, /* is_collapsed_table_borders */
734        );
735        let painter = super::background::BackgroundPainter {
736            style: &source_style,
737            painting_area_override: Some(painting_area),
738            positioning_area_override: None,
739        };
740        fragment_builder.build_background_image(builder, &painter);
741    }
742
743    /// Build a display list from a a [`StackingContext`]. Note that this is the forward
744    /// version of the reversed stacking context walk algorithm in `hit_test.rs`. Any
745    /// changes made here should be reflected in the reverse version in that file.
746    pub(crate) fn build_display_list(&self, builder: &mut DisplayListBuilder) {
747        let pushed_context = self.push_webrender_stacking_context_if_necessary(builder);
748
749        // Properly order display items that make up a stacking context.
750        // “Steps” here refer to the steps in CSS 2.1 Appendix E.
751        // Note that “positioned descendants” is generalised to include all descendants that
752        // generate stacking contexts (csswg-drafts#2717), except in the phrase “any positioned
753        // descendants or descendants that actually create a stacking context”, where the term
754        // means positioned descendants that do not generate stacking contexts.
755
756        // Steps 1 and 2: Borders and background for the root
757        let mut content_with_outlines = Vec::new();
758        let mut contents = self.contents.iter().enumerate().peekable();
759        while contents.peek().is_some_and(|(_, child)| {
760            child.section() == StackingContextSection::OwnBackgroundsAndBorders
761        }) {
762            let (i, child) = contents.next().unwrap();
763            self.debug_push_print_item(DebugPrintField::Contents, i);
764            child.build_display_list(builder, &self.atomic_inline_stacking_containers);
765
766            if child.has_outline() {
767                content_with_outlines.push(child);
768            }
769        }
770
771        // Step 3: Stacking contexts with negative ‘z-index’
772        let mut real_stacking_contexts_and_positioned_stacking_containers = self
773            .real_stacking_contexts_and_positioned_stacking_containers
774            .iter()
775            .enumerate()
776            .peekable();
777        while real_stacking_contexts_and_positioned_stacking_containers
778            .peek()
779            .is_some_and(|(_, child)| child.z_index() < 0)
780        {
781            let (i, child) = real_stacking_contexts_and_positioned_stacking_containers
782                .next()
783                .unwrap();
784            self.debug_push_print_item(
785                DebugPrintField::RealStackingContextsAndPositionedStackingContainers,
786                i,
787            );
788            child.build_display_list(builder);
789        }
790
791        // Step 4: Block backgrounds and borders
792        while contents.peek().is_some_and(|(_, child)| {
793            child.section() == StackingContextSection::DescendantBackgroundsAndBorders
794        }) {
795            let (i, child) = contents.next().unwrap();
796            self.debug_push_print_item(DebugPrintField::Contents, i);
797            child.build_display_list(builder, &self.atomic_inline_stacking_containers);
798
799            if child.has_outline() {
800                content_with_outlines.push(child);
801            }
802        }
803
804        // Step 5: Float stacking containers
805        for (i, child) in self.float_stacking_containers.iter().enumerate() {
806            self.debug_push_print_item(DebugPrintField::FloatStackingContainers, i);
807            child.build_display_list(builder);
808        }
809
810        // Steps 6 and 7: Fragments and inline stacking containers
811        while contents
812            .peek()
813            .is_some_and(|(_, child)| child.section() == StackingContextSection::Foreground)
814        {
815            let (i, child) = contents.next().unwrap();
816            self.debug_push_print_item(DebugPrintField::Contents, i);
817            child.build_display_list(builder, &self.atomic_inline_stacking_containers);
818
819            if child.has_outline() {
820                content_with_outlines.push(child);
821            }
822        }
823
824        // Steps 8 and 9: Stacking contexts with non-negative ‘z-index’, and
825        // positioned stacking containers (where ‘z-index’ is auto)
826        for (i, child) in real_stacking_contexts_and_positioned_stacking_containers {
827            self.debug_push_print_item(
828                DebugPrintField::RealStackingContextsAndPositionedStackingContainers,
829                i,
830            );
831            child.build_display_list(builder);
832        }
833
834        // Step 10: Outline
835        for content in content_with_outlines {
836            content.build_display_list_with_section_override(
837                builder,
838                &self.atomic_inline_stacking_containers,
839                Some(StackingContextSection::Outline),
840            );
841        }
842
843        if pushed_context {
844            builder.wr().pop_stacking_context();
845        }
846    }
847
848    /// Store the fact that something was painted, if [Self::debug_print_items] is not None.
849    ///
850    /// This is used to help reconstruct the original painting order in [Self::debug_print] without
851    /// duplicating our painting order logic, since that could fall out of sync with the real logic.
852    fn debug_push_print_item(&self, field: DebugPrintField, index: usize) {
853        if let Some(items) = self.debug_print_items.as_ref() {
854            items.borrow_mut().push(DebugPrintItem { field, index });
855        }
856    }
857
858    /// Print the stacking context tree.
859    pub fn debug_print(&self) {
860        if self.debug_print_items.is_none() {
861            warn!("failed to print stacking context tree: debug_print_items was None");
862            return;
863        }
864        let mut tree = PrintTree::new("Stacking context tree".to_owned());
865        self.debug_print_with_tree(&mut tree);
866    }
867
868    /// Print a subtree with the given [PrintTree], or panic if [Self::debug_print_items] is None.
869    fn debug_print_with_tree(&self, tree: &mut PrintTree) {
870        match self.context_type {
871            StackingContextType::RealStackingContext => {
872                tree.new_level(format!("{:?} z={}", self.context_type, self.z_index()));
873            },
874            StackingContextType::AtomicInlineStackingContainer => {
875                // do nothing; we print the heading with its index in DebugPrintField::Contents
876            },
877            _ => {
878                tree.new_level(format!("{:?}", self.context_type));
879            },
880        }
881        for DebugPrintItem { field, index } in
882            self.debug_print_items.as_ref().unwrap().borrow().iter()
883        {
884            match field {
885                DebugPrintField::Contents => match self.contents[*index] {
886                    StackingContextContent::Fragment { section, .. } => {
887                        tree.add_item(format!("{section:?}"));
888                    },
889                    StackingContextContent::AtomicInlineStackingContainer { index } => {
890                        tree.new_level(format!("AtomicInlineStackingContainer #{index}"));
891                        self.atomic_inline_stacking_containers[index].debug_print_with_tree(tree);
892                        tree.end_level();
893                    },
894                },
895                DebugPrintField::RealStackingContextsAndPositionedStackingContainers => {
896                    self.real_stacking_contexts_and_positioned_stacking_containers[*index]
897                        .debug_print_with_tree(tree);
898                },
899                DebugPrintField::FloatStackingContainers => {
900                    self.float_stacking_containers[*index].debug_print_with_tree(tree);
901                },
902            }
903        }
904        match self.context_type {
905            StackingContextType::AtomicInlineStackingContainer => {
906                // do nothing; we print the heading with its index in DebugPrintField::Contents
907            },
908            _ => {
909                tree.end_level();
910            },
911        }
912    }
913}
914
915#[derive(PartialEq)]
916pub(crate) enum StackingContextBuildMode {
917    IncludeHoisted,
918    SkipHoisted,
919}
920
921impl Fragment {
922    pub(crate) fn build_stacking_context_tree(
923        &self,
924        stacking_context_tree: &mut StackingContextTree,
925        containing_block_info: &ContainingBlockInfo,
926        stacking_context: &mut StackingContext,
927        mode: StackingContextBuildMode,
928        text_decorations: &Arc<Vec<FragmentTextDecoration>>,
929    ) {
930        if self
931            .base()
932            .is_some_and(|base| base.flags.contains(FragmentFlags::IS_COLLAPSED))
933        {
934            return;
935        }
936
937        let containing_block = containing_block_info.get_containing_block_for_fragment(self);
938        let fragment_clone = self.clone();
939        match self {
940            Fragment::Box(fragment) | Fragment::Float(fragment) => {
941                let fragment = fragment.borrow();
942                if mode == StackingContextBuildMode::SkipHoisted &&
943                    fragment.style().clone_position().is_absolutely_positioned()
944                {
945                    return;
946                }
947
948                let text_decorations = match self {
949                    Fragment::Float(..) => &Default::default(),
950                    _ => text_decorations,
951                };
952
953                fragment.build_stacking_context_tree(
954                    fragment_clone,
955                    stacking_context_tree,
956                    containing_block,
957                    containing_block_info,
958                    stacking_context,
959                    text_decorations,
960                );
961            },
962            Fragment::AbsoluteOrFixedPositioned(fragment) => {
963                let shared_fragment = fragment.borrow();
964                let fragment_ref = match shared_fragment.fragment.as_ref() {
965                    Some(fragment_ref) => fragment_ref,
966                    None => unreachable!("Found hoisted box with missing fragment."),
967                };
968
969                fragment_ref.build_stacking_context_tree(
970                    stacking_context_tree,
971                    containing_block_info,
972                    stacking_context,
973                    StackingContextBuildMode::IncludeHoisted,
974                    &Default::default(),
975                );
976            },
977            Fragment::Positioning(fragment) => {
978                let fragment = fragment.borrow();
979                fragment.build_stacking_context_tree(
980                    stacking_context_tree,
981                    containing_block,
982                    containing_block_info,
983                    stacking_context,
984                    text_decorations,
985                );
986            },
987            Fragment::Text(_) | Fragment::Image(_) | Fragment::IFrame(_) => {
988                stacking_context
989                    .contents
990                    .push(StackingContextContent::Fragment {
991                        section: StackingContextSection::Foreground,
992                        scroll_node_id: containing_block.scroll_node_id,
993                        reference_frame_scroll_node_id: containing_block_info
994                            .for_absolute_and_fixed_descendants
995                            .scroll_node_id,
996                        clip_id: containing_block.clip_id,
997                        containing_block: containing_block.rect,
998                        fragment: fragment_clone,
999                        is_hit_test_for_scrollable_overflow: false,
1000                        is_collapsed_table_borders: false,
1001                        text_decorations: text_decorations.clone(),
1002                    });
1003            },
1004        }
1005    }
1006}
1007
1008struct ReferenceFrameData {
1009    origin: PhysicalPoint<Au>,
1010    transform: LayoutTransform,
1011    kind: wr::ReferenceFrameKind,
1012}
1013struct ScrollFrameData {
1014    scroll_tree_node_id: ScrollTreeNodeId,
1015    scroll_frame_rect: LayoutRect,
1016}
1017
1018struct OverflowFrameData {
1019    clip_id: ClipId,
1020    scroll_frame_data: Option<ScrollFrameData>,
1021}
1022
1023impl BoxFragment {
1024    fn get_stacking_context_type(&self) -> Option<StackingContextType> {
1025        let flags = self.base.flags;
1026        let style = self.style();
1027        if style.establishes_stacking_context(flags) {
1028            return Some(StackingContextType::RealStackingContext);
1029        }
1030
1031        let box_style = &style.get_box();
1032        if box_style.position != ComputedPosition::Static {
1033            return Some(StackingContextType::PositionedStackingContainer);
1034        }
1035
1036        if box_style.float != ComputedFloat::None {
1037            return Some(StackingContextType::FloatStackingContainer);
1038        }
1039
1040        // Flex and grid items are painted like inline blocks.
1041        // <https://drafts.csswg.org/css-flexbox-1/#painting>
1042        // <https://drafts.csswg.org/css-grid/#z-order>
1043        if self.is_atomic_inline_level() || flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) {
1044            return Some(StackingContextType::AtomicInlineStackingContainer);
1045        }
1046
1047        None
1048    }
1049
1050    fn get_stacking_context_section(&self) -> StackingContextSection {
1051        if self.get_stacking_context_type().is_some() {
1052            return StackingContextSection::OwnBackgroundsAndBorders;
1053        }
1054
1055        if self.style().get_box().display.outside() == DisplayOutside::Inline {
1056            return StackingContextSection::Foreground;
1057        }
1058
1059        StackingContextSection::DescendantBackgroundsAndBorders
1060    }
1061
1062    fn build_stacking_context_tree(
1063        &self,
1064        fragment: Fragment,
1065        stacking_context_tree: &mut StackingContextTree,
1066        containing_block: &ContainingBlock,
1067        containing_block_info: &ContainingBlockInfo,
1068        parent_stacking_context: &mut StackingContext,
1069        text_decorations: &Arc<Vec<FragmentTextDecoration>>,
1070    ) {
1071        self.build_stacking_context_tree_maybe_creating_reference_frame(
1072            fragment,
1073            stacking_context_tree,
1074            containing_block,
1075            containing_block_info,
1076            parent_stacking_context,
1077            text_decorations,
1078        );
1079    }
1080
1081    fn build_stacking_context_tree_maybe_creating_reference_frame(
1082        &self,
1083        fragment: Fragment,
1084        stacking_context_tree: &mut StackingContextTree,
1085        containing_block: &ContainingBlock,
1086        containing_block_info: &ContainingBlockInfo,
1087        parent_stacking_context: &mut StackingContext,
1088        text_decorations: &Arc<Vec<FragmentTextDecoration>>,
1089    ) {
1090        let reference_frame_data =
1091            match self.reference_frame_data_if_necessary(&containing_block.rect) {
1092                Some(reference_frame_data) => reference_frame_data,
1093                None => {
1094                    return self.build_stacking_context_tree_maybe_creating_stacking_context(
1095                        fragment,
1096                        stacking_context_tree,
1097                        containing_block,
1098                        containing_block_info,
1099                        parent_stacking_context,
1100                        text_decorations,
1101                    );
1102                },
1103            };
1104
1105        // <https://drafts.csswg.org/css-transforms/#transform-function-lists>
1106        // > If a transform function causes the current transformation matrix of an object
1107        // > to be non-invertible, the object and its content do not get displayed.
1108        if !reference_frame_data.transform.is_invertible() {
1109            self.clear_spatial_tree_node_including_descendants();
1110            return;
1111        }
1112
1113        let style = self.style();
1114        let frame_origin_for_query = self.cumulative_border_box_rect().origin.to_webrender();
1115        let new_spatial_id = stacking_context_tree.push_reference_frame(
1116            reference_frame_data.origin.to_webrender(),
1117            frame_origin_for_query,
1118            containing_block.scroll_node_id,
1119            style.get_box().transform_style.to_webrender(),
1120            reference_frame_data.transform,
1121            reference_frame_data.kind,
1122        );
1123
1124        // WebRender reference frames establish a new coordinate system at their
1125        // origin (the border box of the fragment). We need to ensure that any
1126        // coordinates we give to WebRender in this reference frame are relative
1127        // to the fragment border box. We do this by adjusting the containing
1128        // block origin. Note that the `for_absolute_descendants` and
1129        // `for_all_absolute_and_fixed_descendants` properties are now bogus,
1130        // but all fragments that establish reference frames also establish
1131        // containing blocks for absolute and fixed descendants, so those
1132        // properties will be replaced before recursing into children.
1133        assert!(style.establishes_containing_block_for_all_descendants(self.base.flags));
1134        let adjusted_containing_block = ContainingBlock::new(
1135            containing_block
1136                .rect
1137                .translate(-reference_frame_data.origin.to_vector()),
1138            new_spatial_id,
1139            None,
1140            containing_block.clip_id,
1141        );
1142        let new_containing_block_info =
1143            containing_block_info.new_for_non_absolute_descendants(&adjusted_containing_block);
1144
1145        self.build_stacking_context_tree_maybe_creating_stacking_context(
1146            fragment,
1147            stacking_context_tree,
1148            &adjusted_containing_block,
1149            &new_containing_block_info,
1150            parent_stacking_context,
1151            text_decorations,
1152        );
1153    }
1154
1155    fn build_stacking_context_tree_maybe_creating_stacking_context(
1156        &self,
1157        fragment: Fragment,
1158        stacking_context_tree: &mut StackingContextTree,
1159        containing_block: &ContainingBlock,
1160        containing_block_info: &ContainingBlockInfo,
1161        parent_stacking_context: &mut StackingContext,
1162        text_decorations: &Arc<Vec<FragmentTextDecoration>>,
1163    ) {
1164        let context_type = match self.get_stacking_context_type() {
1165            Some(context_type) => context_type,
1166            None => {
1167                self.build_stacking_context_tree_for_children(
1168                    fragment,
1169                    stacking_context_tree,
1170                    containing_block,
1171                    containing_block_info,
1172                    parent_stacking_context,
1173                    text_decorations,
1174                );
1175                return;
1176            },
1177        };
1178
1179        if context_type == StackingContextType::AtomicInlineStackingContainer {
1180            // Push a dummy fragment that indicates when the new stacking context should be painted.
1181            parent_stacking_context.contents.push(
1182                StackingContextContent::AtomicInlineStackingContainer {
1183                    index: parent_stacking_context
1184                        .atomic_inline_stacking_containers
1185                        .len(),
1186                },
1187            );
1188        }
1189
1190        // `clip-path` needs to be applied before filters and creates a stacking context, so it can be
1191        // applied directly to the stacking context itself.
1192        // before
1193        let stacking_context_clip_id = stacking_context_tree
1194            .clip_store
1195            .add_for_clip_path(
1196                self.style().clone_clip_path(),
1197                containing_block.scroll_node_id,
1198                containing_block.clip_id,
1199                BuilderForBoxFragment::new(
1200                    self,
1201                    &containing_block.rect,
1202                    false, /* is_hit_test_for_scrollable_overflow */
1203                    false, /* is_collapsed_table_borders */
1204                ),
1205            )
1206            .unwrap_or(containing_block.clip_id);
1207
1208        let box_fragment = match fragment {
1209            Fragment::Box(ref box_fragment) | Fragment::Float(ref box_fragment) => {
1210                box_fragment.clone()
1211            },
1212            _ => unreachable!("Should never try to make stacking context for non-BoxFragment"),
1213        };
1214
1215        let mut child_stacking_context = parent_stacking_context.create_descendant(
1216            containing_block.scroll_node_id,
1217            stacking_context_clip_id,
1218            box_fragment,
1219            context_type,
1220        );
1221        self.build_stacking_context_tree_for_children(
1222            fragment,
1223            stacking_context_tree,
1224            containing_block,
1225            containing_block_info,
1226            &mut child_stacking_context,
1227            text_decorations,
1228        );
1229
1230        let mut stolen_children = vec![];
1231        if context_type != StackingContextType::RealStackingContext {
1232            stolen_children = mem::replace(
1233                &mut child_stacking_context
1234                    .real_stacking_contexts_and_positioned_stacking_containers,
1235                stolen_children,
1236            );
1237        }
1238
1239        child_stacking_context.sort();
1240        parent_stacking_context.add_stacking_context(child_stacking_context);
1241        parent_stacking_context
1242            .real_stacking_contexts_and_positioned_stacking_containers
1243            .append(&mut stolen_children);
1244    }
1245
1246    fn build_stacking_context_tree_for_children(
1247        &self,
1248        fragment: Fragment,
1249        stacking_context_tree: &mut StackingContextTree,
1250        containing_block: &ContainingBlock,
1251        containing_block_info: &ContainingBlockInfo,
1252        stacking_context: &mut StackingContext,
1253        text_decorations: &Arc<Vec<FragmentTextDecoration>>,
1254    ) {
1255        let mut new_scroll_node_id = containing_block.scroll_node_id;
1256        let mut new_clip_id = containing_block.clip_id;
1257        let mut new_scroll_frame_size = containing_block_info
1258            .for_non_absolute_descendants
1259            .scroll_frame_size;
1260
1261        if let Some(scroll_node_id) = self.build_sticky_frame_if_necessary(
1262            stacking_context_tree,
1263            new_scroll_node_id,
1264            &containing_block.rect,
1265            &new_scroll_frame_size,
1266        ) {
1267            new_scroll_node_id = scroll_node_id;
1268        }
1269
1270        if let Some(clip_id) = self.build_clip_frame_if_necessary(
1271            stacking_context_tree,
1272            new_scroll_node_id,
1273            new_clip_id,
1274            &containing_block.rect,
1275        ) {
1276            new_clip_id = clip_id;
1277        }
1278
1279        let style = self.style();
1280        if let Some(clip_id) = stacking_context_tree.clip_store.add_for_clip_path(
1281            style.clone_clip_path(),
1282            new_scroll_node_id,
1283            new_clip_id,
1284            BuilderForBoxFragment::new(
1285                self,
1286                &containing_block.rect,
1287                false, /* is_hit_test_for_scrollable_overflow */
1288                false, /* is_collapsed_table_borders */
1289            ),
1290        ) {
1291            new_clip_id = clip_id;
1292        }
1293
1294        let establishes_containing_block_for_all_descendants =
1295            style.establishes_containing_block_for_all_descendants(self.base.flags);
1296        let establishes_containing_block_for_absolute_descendants =
1297            style.establishes_containing_block_for_absolute_descendants(self.base.flags);
1298
1299        let reference_frame_scroll_node_id_for_fragments =
1300            if establishes_containing_block_for_all_descendants {
1301                new_scroll_node_id
1302            } else {
1303                containing_block_info
1304                    .for_absolute_and_fixed_descendants
1305                    .scroll_node_id
1306            };
1307
1308        let mut add_fragment = |section| {
1309            stacking_context
1310                .contents
1311                .push(StackingContextContent::Fragment {
1312                    scroll_node_id: new_scroll_node_id,
1313                    reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments,
1314                    clip_id: new_clip_id,
1315                    section,
1316                    containing_block: containing_block.rect,
1317                    fragment: fragment.clone(),
1318                    is_hit_test_for_scrollable_overflow: false,
1319                    is_collapsed_table_borders: false,
1320                    text_decorations: text_decorations.clone(),
1321                });
1322        };
1323
1324        let section = self.get_stacking_context_section();
1325        add_fragment(section);
1326
1327        // Spatial tree node that will affect the transform of the fragment. Note that the next frame,
1328        // scroll frame, does not affect the transform of the fragment but affect the transform of it
1329        // children.
1330        *self.spatial_tree_node.borrow_mut() = Some(new_scroll_node_id);
1331
1332        // We want to build the scroll frame after the background and border, because
1333        // they shouldn't scroll with the rest of the box content.
1334        if let Some(overflow_frame_data) = self.build_overflow_frame_if_necessary(
1335            stacking_context_tree,
1336            new_scroll_node_id,
1337            new_clip_id,
1338            &containing_block.rect,
1339        ) {
1340            new_clip_id = overflow_frame_data.clip_id;
1341            if let Some(scroll_frame_data) = overflow_frame_data.scroll_frame_data {
1342                new_scroll_node_id = scroll_frame_data.scroll_tree_node_id;
1343                new_scroll_frame_size = Some(scroll_frame_data.scroll_frame_rect.size());
1344                stacking_context
1345                    .contents
1346                    .push(StackingContextContent::Fragment {
1347                        scroll_node_id: new_scroll_node_id,
1348                        reference_frame_scroll_node_id:
1349                            reference_frame_scroll_node_id_for_fragments,
1350                        clip_id: new_clip_id,
1351                        section,
1352                        containing_block: containing_block.rect,
1353                        fragment: fragment.clone(),
1354                        is_hit_test_for_scrollable_overflow: true,
1355                        is_collapsed_table_borders: false,
1356                        text_decorations: text_decorations.clone(),
1357                    });
1358            }
1359        }
1360
1361        let padding_rect = self
1362            .padding_rect()
1363            .translate(containing_block.rect.origin.to_vector());
1364        let content_rect = self
1365            .content_rect()
1366            .translate(containing_block.rect.origin.to_vector());
1367
1368        let for_absolute_descendants = ContainingBlock::new(
1369            padding_rect,
1370            new_scroll_node_id,
1371            new_scroll_frame_size,
1372            new_clip_id,
1373        );
1374        let for_non_absolute_descendants = ContainingBlock::new(
1375            content_rect,
1376            new_scroll_node_id,
1377            new_scroll_frame_size,
1378            new_clip_id,
1379        );
1380
1381        // Create a new `ContainingBlockInfo` for descendants depending on
1382        // whether or not this fragment establishes a containing block for
1383        // absolute and fixed descendants.
1384        let new_containing_block_info = if establishes_containing_block_for_all_descendants {
1385            containing_block_info.new_for_absolute_and_fixed_descendants(
1386                &for_non_absolute_descendants,
1387                &for_absolute_descendants,
1388            )
1389        } else if establishes_containing_block_for_absolute_descendants {
1390            containing_block_info.new_for_absolute_descendants(
1391                &for_non_absolute_descendants,
1392                &for_absolute_descendants,
1393            )
1394        } else {
1395            containing_block_info.new_for_non_absolute_descendants(&for_non_absolute_descendants)
1396        };
1397
1398        // Text decorations are not propagated to atomic inline-level descendants.
1399        // From https://drafts.csswg.org/css2/#lining-striking-props:
1400        // > Note that text decorations are not propagated to floating and absolutely
1401        // > positioned descendants, nor to the contents of atomic inline-level descendants
1402        // > such as inline blocks and inline tables.
1403        let text_decorations = match self.is_atomic_inline_level() ||
1404            self.base
1405                .flags
1406                .contains(FragmentFlags::IS_OUTSIDE_LIST_ITEM_MARKER)
1407        {
1408            true => &Default::default(),
1409            false => text_decorations,
1410        };
1411
1412        let new_text_decoration;
1413        let text_decorations = match style.clone_text_decoration_line() {
1414            TextDecorationLine::NONE => text_decorations,
1415            line => {
1416                let mut new_vector = (**text_decorations).clone();
1417                let color = &style.get_inherited_text().color;
1418                new_vector.push(FragmentTextDecoration {
1419                    line,
1420                    color: style
1421                        .clone_text_decoration_color()
1422                        .resolve_to_absolute(color),
1423                    style: style.clone_text_decoration_style(),
1424                });
1425                new_text_decoration = Arc::new(new_vector);
1426                &new_text_decoration
1427            },
1428        };
1429
1430        for child in &self.children {
1431            child.build_stacking_context_tree(
1432                stacking_context_tree,
1433                &new_containing_block_info,
1434                stacking_context,
1435                StackingContextBuildMode::SkipHoisted,
1436                text_decorations,
1437            );
1438        }
1439
1440        if matches!(
1441            self.specific_layout_info().as_deref(),
1442            Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(_))
1443        ) {
1444            stacking_context
1445                .contents
1446                .push(StackingContextContent::Fragment {
1447                    scroll_node_id: new_scroll_node_id,
1448                    reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments,
1449                    clip_id: new_clip_id,
1450                    section,
1451                    containing_block: containing_block.rect,
1452                    fragment: fragment.clone(),
1453                    is_hit_test_for_scrollable_overflow: false,
1454                    is_collapsed_table_borders: true,
1455                    text_decorations: text_decorations.clone(),
1456                });
1457        }
1458    }
1459
1460    fn build_clip_frame_if_necessary(
1461        &self,
1462        stacking_context_tree: &mut StackingContextTree,
1463        parent_scroll_node_id: ScrollTreeNodeId,
1464        parent_clip_id: ClipId,
1465        containing_block_rect: &PhysicalRect<Au>,
1466    ) -> Option<ClipId> {
1467        let style = self.style();
1468        let position = style.get_box().position;
1469        // https://drafts.csswg.org/css2/#clipping
1470        // The clip property applies only to absolutely positioned elements
1471        if !position.is_absolutely_positioned() {
1472            return None;
1473        }
1474
1475        // Only rectangles are supported for now.
1476        let clip_rect = match style.get_effects().clip {
1477            ClipRectOrAuto::Rect(rect) => rect,
1478            _ => return None,
1479        };
1480
1481        let border_rect = self.border_rect();
1482        let clip_rect = clip_rect
1483            .for_border_rect(border_rect)
1484            .translate(containing_block_rect.origin.to_vector())
1485            .to_webrender();
1486        Some(stacking_context_tree.clip_store.add(
1487            BorderRadius::zero(),
1488            clip_rect,
1489            parent_scroll_node_id,
1490            parent_clip_id,
1491        ))
1492    }
1493
1494    fn build_overflow_frame_if_necessary(
1495        &self,
1496        stacking_context_tree: &mut StackingContextTree,
1497        parent_scroll_node_id: ScrollTreeNodeId,
1498        parent_clip_id: ClipId,
1499        containing_block_rect: &PhysicalRect<Au>,
1500    ) -> Option<OverflowFrameData> {
1501        let style = self.style();
1502        let overflow = style.effective_overflow(self.base.flags);
1503
1504        if overflow.x == ComputedOverflow::Visible && overflow.y == ComputedOverflow::Visible {
1505            return None;
1506        }
1507
1508        // Non-scrollable overflow path
1509        if overflow.x == ComputedOverflow::Clip || overflow.y == ComputedOverflow::Clip {
1510            let overflow_clip_margin = style.get_margin().overflow_clip_margin;
1511            let mut overflow_clip_rect = match overflow_clip_margin.visual_box {
1512                OverflowClipMarginBox::ContentBox => self.content_rect(),
1513                OverflowClipMarginBox::PaddingBox => self.padding_rect(),
1514                OverflowClipMarginBox::BorderBox => self.border_rect(),
1515            }
1516            .translate(containing_block_rect.origin.to_vector())
1517            .to_webrender();
1518
1519            // Adjust by the overflow clip margin.
1520            // https://drafts.csswg.org/css-overflow-3/#overflow-clip-margin
1521            let clip_margin_offset = overflow_clip_margin.offset.px();
1522            overflow_clip_rect = overflow_clip_rect.inflate(clip_margin_offset, clip_margin_offset);
1523
1524            // The clipping region only gets rounded corners if both axes have `overflow: clip`.
1525            // https://drafts.csswg.org/css-overflow-3/#corner-clipping
1526            let radii;
1527            if overflow.x == ComputedOverflow::Clip && overflow.y == ComputedOverflow::Clip {
1528                let builder = BuilderForBoxFragment::new(self, containing_block_rect, false, false);
1529                let mut offsets_from_border = SideOffsets2D::new_all_same(clip_margin_offset);
1530                match overflow_clip_margin.visual_box {
1531                    OverflowClipMarginBox::ContentBox => {
1532                        offsets_from_border -= (self.border + self.padding).to_webrender();
1533                    },
1534                    OverflowClipMarginBox::PaddingBox => {
1535                        offsets_from_border -= self.border.to_webrender();
1536                    },
1537                    OverflowClipMarginBox::BorderBox => {},
1538                };
1539                radii = offset_radii(builder.border_radius, offsets_from_border);
1540            } else if overflow.x != ComputedOverflow::Clip {
1541                overflow_clip_rect.min.x = f32::MIN;
1542                overflow_clip_rect.max.x = f32::MAX;
1543                radii = BorderRadius::zero();
1544            } else {
1545                overflow_clip_rect.min.y = f32::MIN;
1546                overflow_clip_rect.max.y = f32::MAX;
1547                radii = BorderRadius::zero();
1548            }
1549
1550            let clip_id = stacking_context_tree.clip_store.add(
1551                radii,
1552                overflow_clip_rect,
1553                parent_scroll_node_id,
1554                parent_clip_id,
1555            );
1556
1557            return Some(OverflowFrameData {
1558                clip_id,
1559                scroll_frame_data: None,
1560            });
1561        }
1562
1563        let scroll_frame_rect = self
1564            .padding_rect()
1565            .translate(containing_block_rect.origin.to_vector())
1566            .to_webrender();
1567
1568        let clip_id = stacking_context_tree.clip_store.add(
1569            BuilderForBoxFragment::new(self, containing_block_rect, false, false).border_radius,
1570            scroll_frame_rect,
1571            parent_scroll_node_id,
1572            parent_clip_id,
1573        );
1574
1575        let tag = self.base.tag?;
1576        let external_scroll_id = wr::ExternalScrollId(
1577            tag.to_display_list_fragment_id(),
1578            stacking_context_tree.paint_info.pipeline_id,
1579        );
1580
1581        let sensitivity = AxesScrollSensitivity {
1582            x: overflow.x.into(),
1583            y: overflow.y.into(),
1584        };
1585
1586        let scroll_tree_node_id = stacking_context_tree.define_scroll_frame(
1587            parent_scroll_node_id,
1588            external_scroll_id,
1589            self.scrollable_overflow().to_webrender(),
1590            scroll_frame_rect,
1591            sensitivity,
1592        );
1593
1594        Some(OverflowFrameData {
1595            clip_id,
1596            scroll_frame_data: Some(ScrollFrameData {
1597                scroll_tree_node_id,
1598                scroll_frame_rect,
1599            }),
1600        })
1601    }
1602
1603    fn build_sticky_frame_if_necessary(
1604        &self,
1605        stacking_context_tree: &mut StackingContextTree,
1606        parent_scroll_node_id: ScrollTreeNodeId,
1607        containing_block_rect: &PhysicalRect<Au>,
1608        scroll_frame_size: &Option<LayoutSize>,
1609    ) -> Option<ScrollTreeNodeId> {
1610        let style = self.style();
1611        if style.get_box().position != ComputedPosition::Sticky {
1612            return None;
1613        }
1614
1615        let scroll_frame_size_for_resolve = match scroll_frame_size {
1616            Some(size) => size,
1617            None => {
1618                // This is a direct descendant of a reference frame.
1619                &stacking_context_tree
1620                    .paint_info
1621                    .viewport_details
1622                    .layout_size()
1623            },
1624        };
1625
1626        // Percentages sticky positions offsets are resovled against the size of the
1627        // nearest scroll frame instead of the containing block like for other types
1628        // of positioning.
1629        let scroll_frame_height = Au::from_f32_px(scroll_frame_size_for_resolve.height);
1630        let scroll_frame_width = Au::from_f32_px(scroll_frame_size_for_resolve.width);
1631        let offsets = style.physical_box_offsets();
1632        let offsets = PhysicalSides::<AuOrAuto>::new(
1633            offsets.top.map(|v| v.to_used_value(scroll_frame_height)),
1634            offsets.right.map(|v| v.to_used_value(scroll_frame_width)),
1635            offsets.bottom.map(|v| v.to_used_value(scroll_frame_height)),
1636            offsets.left.map(|v| v.to_used_value(scroll_frame_width)),
1637        );
1638        self.ensure_rare_data().resolved_sticky_insets = Some(Box::new(offsets));
1639
1640        if scroll_frame_size.is_none() {
1641            return None;
1642        }
1643
1644        if offsets.top.is_auto() &&
1645            offsets.right.is_auto() &&
1646            offsets.bottom.is_auto() &&
1647            offsets.left.is_auto()
1648        {
1649            return None;
1650        }
1651
1652        // https://drafts.csswg.org/css-position/#stickypos-insets
1653        // > For each side of the box, if the corresponding inset property is not `auto`, and the
1654        // > corresponding border edge of the box would be outside the corresponding edge of the
1655        // > sticky view rectangle, the box must be visually shifted (as for relative positioning)
1656        // > to be inward of that sticky view rectangle edge, insofar as it can while its position
1657        // > box remains contained within its containing block.
1658        // > The *position box* is its margin box, except that for any side for which the distance
1659        // > between its margin edge and the corresponding edge of its containing block is less
1660        // > than its corresponding margin, that distance is used in place of that margin.
1661        //
1662        // Amendments:
1663        // - Using the "margin edge" seems nonsensical, the spec must mean "border edge" instead:
1664        //   https://github.com/w3c/csswg-drafts/issues/12833
1665        // - `auto` margins need to be treated as zero:
1666        //   https://github.com/w3c/csswg-drafts/issues/12852
1667        //
1668        // We implement this by enforcing a minimum negative offset and a maximum positive offset.
1669        // The logic below is a simplified (but equivalent) version of the description above.
1670        let border_rect = self.border_rect();
1671        let computed_margin = style.physical_margin();
1672
1673        // Signed distance between each side of the border box to the corresponding side of the
1674        // containing block. Note that |border_rect| is already in the coordinate system of the
1675        // containing block.
1676        let distance_from_border_box_to_cb = PhysicalSides::new(
1677            border_rect.min_y(),
1678            containing_block_rect.width() - border_rect.max_x(),
1679            containing_block_rect.height() - border_rect.max_y(),
1680            border_rect.min_x(),
1681        );
1682
1683        // Shrinks the signed distance by the margin, producing a limit on how much we can shift
1684        // the sticky positioned box without forcing the margin to move outside of the containing
1685        // block.
1686        let offset_bound = |distance, used_margin, computed_margin: LengthPercentageOrAuto| {
1687            let used_margin = if computed_margin.is_auto() {
1688                Au::zero()
1689            } else {
1690                used_margin
1691            };
1692            Au::zero().max(distance - used_margin).to_f32_px()
1693        };
1694
1695        // This is the minimum negative offset and then the maximum positive offset. We specify
1696        // all sides, but they will have no effect if the corresponding inset property is `auto`.
1697        let vertical_offset_bounds = wr::StickyOffsetBounds::new(
1698            -offset_bound(
1699                distance_from_border_box_to_cb.top,
1700                self.margin.top,
1701                computed_margin.top,
1702            ),
1703            offset_bound(
1704                distance_from_border_box_to_cb.bottom,
1705                self.margin.bottom,
1706                computed_margin.bottom,
1707            ),
1708        );
1709        let horizontal_offset_bounds = wr::StickyOffsetBounds::new(
1710            -offset_bound(
1711                distance_from_border_box_to_cb.left,
1712                self.margin.left,
1713                computed_margin.left,
1714            ),
1715            offset_bound(
1716                distance_from_border_box_to_cb.right,
1717                self.margin.right,
1718                computed_margin.right,
1719            ),
1720        );
1721
1722        let frame_rect = border_rect
1723            .translate(containing_block_rect.origin.to_vector())
1724            .to_webrender();
1725
1726        // These are the "margins" between the scrollport and |frame_rect|. They are not the same
1727        // as CSS margins.
1728        let margins = SideOffsets2D::new(
1729            offsets.top.non_auto().map(|v| v.to_f32_px()),
1730            offsets.right.non_auto().map(|v| v.to_f32_px()),
1731            offsets.bottom.non_auto().map(|v| v.to_f32_px()),
1732            offsets.left.non_auto().map(|v| v.to_f32_px()),
1733        );
1734
1735        let sticky_node_id = stacking_context_tree.define_sticky_frame(
1736            parent_scroll_node_id,
1737            frame_rect,
1738            margins,
1739            vertical_offset_bounds,
1740            horizontal_offset_bounds,
1741        );
1742
1743        Some(sticky_node_id)
1744    }
1745
1746    /// Optionally returns the data for building a reference frame, without yet building it.
1747    fn reference_frame_data_if_necessary(
1748        &self,
1749        containing_block_rect: &PhysicalRect<Au>,
1750    ) -> Option<ReferenceFrameData> {
1751        if !self
1752            .style()
1753            .has_effective_transform_or_perspective(self.base.flags)
1754        {
1755            return None;
1756        }
1757
1758        let relative_border_rect = self.border_rect();
1759        let border_rect = relative_border_rect.translate(containing_block_rect.origin.to_vector());
1760        let transform = self.calculate_transform_matrix(&border_rect);
1761        let perspective = self.calculate_perspective_matrix(&border_rect);
1762        let (reference_frame_transform, reference_frame_kind) = match (transform, perspective) {
1763            (None, Some(perspective)) => (
1764                perspective,
1765                wr::ReferenceFrameKind::Perspective {
1766                    scrolling_relative_to: None,
1767                },
1768            ),
1769            (Some(transform), None) => (
1770                transform,
1771                wr::ReferenceFrameKind::Transform {
1772                    is_2d_scale_translation: false,
1773                    should_snap: false,
1774                    paired_with_perspective: false,
1775                },
1776            ),
1777            (Some(transform), Some(perspective)) => (
1778                perspective.then(&transform),
1779                wr::ReferenceFrameKind::Perspective {
1780                    scrolling_relative_to: None,
1781                },
1782            ),
1783            (None, None) => unreachable!(),
1784        };
1785
1786        Some(ReferenceFrameData {
1787            origin: border_rect.origin,
1788            transform: reference_frame_transform,
1789            kind: reference_frame_kind,
1790        })
1791    }
1792
1793    /// Returns the 4D matrix representing this fragment's transform.
1794    pub fn calculate_transform_matrix(
1795        &self,
1796        border_rect: &Rect<Au, CSSPixel>,
1797    ) -> Option<LayoutTransform> {
1798        let style = self.style();
1799        let list = &style.get_box().transform;
1800        let length_rect = au_rect_to_length_rect(border_rect);
1801        // https://drafts.csswg.org/css-transforms-2/#individual-transforms
1802        let rotate = match style.clone_rotate() {
1803            GenericRotate::Rotate(angle) => (0., 0., 1., angle),
1804            GenericRotate::Rotate3D(x, y, z, angle) => (x, y, z, angle),
1805            GenericRotate::None => (0., 0., 1., Angle::zero()),
1806        };
1807        let scale = match style.clone_scale() {
1808            GenericScale::Scale(sx, sy, sz) => (sx, sy, sz),
1809            GenericScale::None => (1., 1., 1.),
1810        };
1811        let translation = match style.clone_translate() {
1812            GenericTranslate::Translate(x, y, z) => LayoutTransform::translation(
1813                x.resolve(length_rect.size.width).px(),
1814                y.resolve(length_rect.size.height).px(),
1815                z.px(),
1816            ),
1817            GenericTranslate::None => LayoutTransform::identity(),
1818        };
1819
1820        let angle = euclid::Angle::radians(rotate.3.radians());
1821        let transform_base = list
1822            .to_transform_3d_matrix(Some(&length_rect.to_untyped()))
1823            .ok()?;
1824        let transform = LayoutTransform::from_untyped(&transform_base.0)
1825            .then_rotate(rotate.0, rotate.1, rotate.2, angle)
1826            .then_scale(scale.0, scale.1, scale.2)
1827            .then(&translation);
1828
1829        let transform_origin = &style.get_box().transform_origin;
1830        let transform_origin_x = transform_origin
1831            .horizontal
1832            .to_used_value(border_rect.size.width)
1833            .to_f32_px();
1834        let transform_origin_y = transform_origin
1835            .vertical
1836            .to_used_value(border_rect.size.height)
1837            .to_f32_px();
1838        let transform_origin_z = transform_origin.depth.px();
1839
1840        Some(transform.change_basis(transform_origin_x, transform_origin_y, transform_origin_z))
1841    }
1842
1843    /// Returns the 4D matrix representing this fragment's perspective.
1844    pub fn calculate_perspective_matrix(
1845        &self,
1846        border_rect: &Rect<Au, CSSPixel>,
1847    ) -> Option<LayoutTransform> {
1848        let style = self.style();
1849        match style.get_box().perspective {
1850            Perspective::Length(length) => {
1851                let perspective_origin = &style.get_box().perspective_origin;
1852                let perspective_origin = LayoutPoint::new(
1853                    perspective_origin
1854                        .horizontal
1855                        .percentage_relative_to(border_rect.size.width.into())
1856                        .px(),
1857                    perspective_origin
1858                        .vertical
1859                        .percentage_relative_to(border_rect.size.height.into())
1860                        .px(),
1861                );
1862
1863                let perspective_matrix = LayoutTransform::from_untyped(
1864                    &transform::create_perspective_matrix(length.px()),
1865                );
1866
1867                Some(perspective_matrix.change_basis(
1868                    perspective_origin.x,
1869                    perspective_origin.y,
1870                    0.0,
1871                ))
1872            },
1873            Perspective::None => None,
1874        }
1875    }
1876
1877    fn clear_spatial_tree_node_including_descendants(&self) {
1878        fn assign_spatial_tree_node_on_fragments(fragments: &[Fragment]) {
1879            for fragment in fragments.iter() {
1880                match fragment {
1881                    Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
1882                        box_fragment
1883                            .borrow()
1884                            .clear_spatial_tree_node_including_descendants();
1885                    },
1886                    Fragment::Positioning(positioning_fragment) => {
1887                        assign_spatial_tree_node_on_fragments(
1888                            &positioning_fragment.borrow().children,
1889                        );
1890                    },
1891                    _ => {},
1892                }
1893            }
1894        }
1895
1896        *self.spatial_tree_node.borrow_mut() = None;
1897        assign_spatial_tree_node_on_fragments(&self.children);
1898    }
1899}
1900
1901impl PositioningFragment {
1902    fn build_stacking_context_tree(
1903        &self,
1904        stacking_context_tree: &mut StackingContextTree,
1905        containing_block: &ContainingBlock,
1906        containing_block_info: &ContainingBlockInfo,
1907        stacking_context: &mut StackingContext,
1908        text_decorations: &Arc<Vec<FragmentTextDecoration>>,
1909    ) {
1910        let rect = self
1911            .base
1912            .rect
1913            .translate(containing_block.rect.origin.to_vector());
1914        let new_containing_block = containing_block.new_replacing_rect(&rect);
1915        let new_containing_block_info =
1916            containing_block_info.new_for_non_absolute_descendants(&new_containing_block);
1917
1918        for child in &self.children {
1919            child.build_stacking_context_tree(
1920                stacking_context_tree,
1921                &new_containing_block_info,
1922                stacking_context,
1923                StackingContextBuildMode::SkipHoisted,
1924                text_decorations,
1925            );
1926        }
1927    }
1928}
1929
1930pub(crate) fn au_rect_to_length_rect(rect: &Rect<Au, CSSPixel>) -> Rect<Length, CSSPixel> {
1931    Rect::new(
1932        Point2D::new(rect.origin.x.into(), rect.origin.y.into()),
1933        Size2D::new(rect.size.width.into(), rect.size.height.into()),
1934    )
1935}