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