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