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