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