Skip to main content

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 std::cell::Cell;
6use std::rc::Rc;
7use std::sync::Arc;
8
9use app_units::Au;
10use embedder_traits::ViewportDetails;
11use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
12use malloc_size_of_derive::MallocSizeOf;
13use paint_api::display_list::{
14    AxesScrollSensitivity, PaintDisplayListInfo, ReferenceFrameNodeInfo, ScrollableNodeInfo,
15    SpatialTreeNodeInfo, StickyNodeInfo,
16};
17use servo_base::id::ScrollTreeNodeId;
18use servo_base::print_tree::PrintTree;
19use servo_config::opts::{DiagnosticsLogging, DiagnosticsLoggingOption};
20use servo_geometry::MaxRect;
21use style::Zero;
22use style::color::AbsoluteColor;
23use style::computed_values::overflow_x::T as ComputedOverflow;
24use style::computed_values::position::T as ComputedPosition;
25use style::computed_values::text_decoration_style::T as TextDecorationStyle;
26use style::computed_values::text_decoration_thickness::T as TextDecorationThickness;
27use style::values::computed::angle::Angle;
28use style::values::computed::{ClipRectOrAuto, Length, TextDecorationLine};
29use style::values::generics::box_::{OverflowClipMarginBox, Perspective};
30use style::values::generics::transform::{
31    self, GenericRotate, GenericScale, GenericTranslate, get_normalized_vector_and_angle,
32};
33use style_traits::CSSPixel;
34use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D};
35use webrender_api::{self as wr, BorderRadius};
36use wr::StickyOffsetBounds;
37use wr::units::{LayoutPixel, LayoutSize};
38
39use super::ClipId;
40use super::clip::StackingContextTreeClipStore;
41use crate::display_list::conversions::ToWebRender;
42use crate::display_list::{BuilderForBoxFragment, offset_radii};
43use crate::fragment_tree::{
44    BoxFragment, BoxFragmentWithStyle, ContainingBlockCalculation, ContainingBlockManager,
45    Fragment, FragmentFlags, FragmentTree, PositioningFragment,
46};
47use crate::geom::{
48    AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize,
49    PhysicalVec,
50};
51use crate::style_ext::{ComputedValuesExt, TransformExt};
52
53#[derive(Clone)]
54pub(crate) struct ContainingBlock {
55    /// The SpatialId of the spatial node that contains the children
56    /// of this containing block.
57    scroll_node_id: ScrollTreeNodeId,
58
59    /// The size of the parent scroll frame of this containing block, used for resolving
60    /// sticky margins. If this is None, then this is a direct descendant of a reference
61    /// frame and sticky positioning isn't taken into account.
62    scroll_frame_size: Option<LayoutSize>,
63
64    /// The [`ClipId`] to use for the children of this containing block.
65    clip_id: ClipId,
66
67    /// The physical rect of this containing block.
68    rect: PhysicalRect<Au>,
69
70    /// Normally containing block offsets and display list items are positioned relative
71    /// to their parent reference frame, but cumulative containing block boundaries on
72    /// fragments need to disregard reference frames entirely. This value tracks the
73    /// accumulated offset from the origin of the parent reference frame of this
74    /// containing block.
75    accumulated_reference_frame_offset: PhysicalVec<Au>,
76
77    /// Whether current containing block established a scroll frame.
78    established_scroll_frame: bool,
79}
80
81impl ContainingBlock {
82    pub(crate) fn new(
83        rect: PhysicalRect<Au>,
84        scroll_node_id: ScrollTreeNodeId,
85        scroll_frame_size: Option<LayoutSize>,
86        clip_id: ClipId,
87        accumulated_reference_frame_offset: PhysicalVec<Au>,
88        established_scroll_frame: bool,
89    ) -> Self {
90        ContainingBlock {
91            scroll_node_id,
92            scroll_frame_size,
93            clip_id,
94            rect,
95            accumulated_reference_frame_offset,
96            established_scroll_frame,
97        }
98    }
99
100    pub(crate) fn new_replacing_rect(&self, rect: &PhysicalRect<Au>) -> Self {
101        ContainingBlock {
102            rect: *rect,
103            ..*self
104        }
105    }
106}
107
108pub(crate) type ContainingBlockInfo<'a> = ContainingBlockManager<'a, ContainingBlock>;
109
110#[derive(MallocSizeOf)]
111pub(crate) struct StackingContextTree {
112    /// The root [`StackingContext`] of this [`StackingContextTree`].
113    pub root_stacking_context: StackingContext,
114
115    /// The information about the WebRender display list that `Paint`
116    /// consumes. This currently contains the out-of-band hit testing information
117    /// data structure that `Paint` uses to map hit tests to information
118    /// about the item hit.
119    pub paint_info: PaintDisplayListInfo,
120
121    /// All of the clips collected for this [`StackingContextTree`]. These are added
122    /// for things like `overflow`. More clips may be created later during WebRender
123    /// display list construction, but they are never added here.
124    pub clip_store: StackingContextTreeClipStore,
125}
126
127impl StackingContextTree {
128    /// Create a new [DisplayList] given the dimensions of the layout and the WebRender
129    /// pipeline id.
130    pub fn new(
131        fragment_tree: &FragmentTree,
132        viewport_details: ViewportDetails,
133        pipeline_id: wr::PipelineId,
134        first_reflow: bool,
135        debug: &DiagnosticsLogging,
136    ) -> Self {
137        let scrollable_overflow = fragment_tree.scrollable_overflow();
138        let scroll_area = scrollable_overflow.union(&fragment_tree.initial_containing_block);
139        let scroll_area = LayoutSize::from_untyped(Size2D::new(
140            scroll_area.size.width.to_f32_px(),
141            scroll_area.size.height.to_f32_px(),
142        ));
143
144        let viewport_size = viewport_details.layout_size();
145        let paint_info = PaintDisplayListInfo::new(
146            viewport_details,
147            scroll_area,
148            pipeline_id,
149            // This epoch is set when the WebRender display list is built. For now use a dummy value.
150            Default::default(),
151            fragment_tree.viewport_scroll_sensitivity,
152            first_reflow,
153        );
154
155        let root_scroll_node_id = paint_info.root_scroll_node_id;
156        let cb_for_non_fixed_descendants = ContainingBlock::new(
157            fragment_tree.initial_containing_block,
158            root_scroll_node_id,
159            Some(viewport_size),
160            ClipId::INVALID,
161            PhysicalVec::zero(),
162            true,
163        );
164        let cb_for_fixed_descendants = ContainingBlock::new(
165            fragment_tree.initial_containing_block,
166            paint_info.root_reference_frame_id,
167            None,
168            ClipId::INVALID,
169            PhysicalVec::zero(),
170            false,
171        );
172
173        // We need to specify all three containing blocks here, because absolute
174        // descendants of the root cannot share the containing block we specify
175        // for fixed descendants. In this case, they need to have the spatial
176        // id of the root scroll frame, whereas fixed descendants need the
177        // spatial id of the root reference frame so that they do not scroll with
178        // page content.
179        let containing_block_info = ContainingBlockInfo {
180            for_non_absolute_descendants: &cb_for_non_fixed_descendants,
181            for_absolute_descendants: Some(&cb_for_non_fixed_descendants),
182            for_absolute_and_fixed_descendants: &cb_for_fixed_descendants,
183        };
184
185        let mut stacking_context_tree = Self {
186            // This is just a temporary value that will be replaced once we have finished
187            // building the tree.
188            root_stacking_context: StackingContext::root(root_scroll_node_id),
189            paint_info,
190            clip_store: Default::default(),
191        };
192
193        let text_decorations = Default::default();
194        let mut root_stacking_context = StackingContext::root(root_scroll_node_id);
195        if let Some(root_box_fragment) = fragment_tree.root_box_fragment() {
196            Fragment::Box(root_box_fragment).build_stacking_context_tree(
197                &mut stacking_context_tree,
198                &containing_block_info,
199                &mut root_stacking_context,
200                // The root element might be absolutely positioned and we still want
201                // to process it, if it is.
202                StackingContextBuildMode::IncludeHoisted,
203                &text_decorations,
204            );
205        }
206
207        root_stacking_context.sort();
208        stacking_context_tree.root_stacking_context = root_stacking_context;
209
210        if debug.is_enabled(DiagnosticsLoggingOption::StackingContextTree) {
211            stacking_context_tree
212                .root_stacking_context
213                .print(&mut PrintTree::new("Stacking Context Tree"));
214        }
215
216        stacking_context_tree
217    }
218
219    fn push_reference_frame(
220        &mut self,
221        origin: LayoutPoint,
222        frame_origin_for_query: LayoutPoint,
223        parent_scroll_node_id: ScrollTreeNodeId,
224        transform_style: wr::TransformStyle,
225        transform: LayoutTransform,
226        kind: wr::ReferenceFrameKind,
227    ) -> ScrollTreeNodeId {
228        self.paint_info.scroll_tree.add_scroll_tree_node(
229            Some(parent_scroll_node_id),
230            SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo {
231                origin,
232                frame_origin_for_query,
233                transform_style,
234                transform: transform.into(),
235                kind,
236            }),
237        )
238    }
239
240    fn define_scroll_frame(
241        &mut self,
242        parent_scroll_node_id: ScrollTreeNodeId,
243        external_id: wr::ExternalScrollId,
244        content_rect: LayoutRect,
245        clip_rect: LayoutRect,
246        scroll_sensitivity: AxesScrollSensitivity,
247    ) -> ScrollTreeNodeId {
248        self.paint_info.scroll_tree.add_scroll_tree_node(
249            Some(parent_scroll_node_id),
250            SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo {
251                external_id,
252                content_rect,
253                clip_rect,
254                scroll_sensitivity,
255                offset: LayoutVector2D::zero(),
256                offset_changed: Cell::new(false),
257            }),
258        )
259    }
260
261    fn define_sticky_frame(
262        &mut self,
263        parent_scroll_node_id: ScrollTreeNodeId,
264        frame_rect: LayoutRect,
265        margins: SideOffsets2D<Option<f32>, LayoutPixel>,
266        vertical_offset_bounds: StickyOffsetBounds,
267        horizontal_offset_bounds: StickyOffsetBounds,
268    ) -> ScrollTreeNodeId {
269        self.paint_info.scroll_tree.add_scroll_tree_node(
270            Some(parent_scroll_node_id),
271            SpatialTreeNodeInfo::Sticky(StickyNodeInfo {
272                frame_rect,
273                margins,
274                vertical_offset_bounds,
275                horizontal_offset_bounds,
276            }),
277        )
278    }
279
280    /// Given a [`Fragment`] and a point in the viewport of the page, return the point in
281    /// the [`Fragment`]'s content rectangle in its transformed coordinate system
282    /// (untransformed CSS pixels). Note that the point may be outside the [`Fragment`]'s
283    /// boundaries.
284    ///
285    /// TODO: Currently, this only works for [`BoxFragment`], but we should extend it to
286    /// other types of [`Fragment`]s in the future.
287    pub(crate) fn offset_in_fragment(
288        &self,
289        fragment: &Fragment,
290        point_in_viewport: PhysicalPoint<Au>,
291    ) -> Option<Point2D<Au, CSSPixel>> {
292        let fragment = fragment.retrieve_box_fragment()?;
293        let spatial_tree_node = fragment.spatial_tree_node()?;
294        let transform = self
295            .paint_info
296            .scroll_tree
297            .cumulative_root_to_node_transform(spatial_tree_node)?;
298        let transformed_point = transform
299            .project_point2d(point_in_viewport.map(Au::to_f32_px).cast_unit())?
300            .map(Au::from_f32_px)
301            .cast_unit();
302
303        // Find the origin of the fragment relative to its reference frame in the same coordinate system.
304        let reference_frame_origin = self
305            .paint_info
306            .scroll_tree
307            .reference_frame_offset(spatial_tree_node)
308            .map(Au::from_f32_px);
309        let fragment_origin = fragment
310            .cumulative_content_box_rect(
311                ContainingBlockCalculation::AlreadyDoneWithStackingContextTree,
312            )
313            .origin -
314            reference_frame_origin.cast_unit();
315
316        // Use that to find the offset from the fragment origin.
317        Some(transformed_point - fragment_origin)
318    }
319}
320
321/// The text decorations for a Fragment, collecting during [`StackingContextTree`] construction.
322#[derive(Clone, Debug, MallocSizeOf)]
323pub(crate) struct FragmentTextDecoration {
324    pub line: TextDecorationLine,
325    pub color: AbsoluteColor,
326    pub style: TextDecorationStyle,
327    pub thickness: TextDecorationThickness,
328}
329
330#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
331pub(crate) enum StackingContextType {
332    StackingContext,
333    StackingContainer,
334}
335
336#[derive(MallocSizeOf)]
337pub(crate) enum StackingContextFragments {
338    Root,
339    Fragment(#[conditional_malloc_size_of] Arc<BoxFragment>),
340}
341
342#[derive(MallocSizeOf)]
343pub(crate) struct StackingContextReferenceFrameInfo {
344    pub(crate) parent_spatial_node_id: ScrollTreeNodeId,
345    pub(crate) captured_clip_id: ClipId,
346}
347
348/// Either a stacking context or a stacking container, per the definitions in
349/// <https://drafts.csswg.org/css-position-4/#painting-order>.
350///
351/// We use the term “real stacking context” in situations that call for a stacking context
352/// but not a stacking container. Only positioned stacking containers every get a
353/// `StackingContext`. The rest are handled inline during the `PaintTraversal`.
354#[derive(MallocSizeOf)]
355pub struct StackingContext {
356    /// The [`BoxFragment`] that established this stacking context. This is used to paint this
357    /// [`StackingContext`] and traverse its descendants for painting.
358    ///
359    /// This is `None` for the root stacking context.
360    pub(crate) fragment: StackingContextFragments,
361
362    /// The [`StackingContextType`] of this [`StackingContet`], which determines if it
363    /// a stacking context or a stacking container.
364    pub(crate) context_type: StackingContextType,
365
366    /// Child [`StackingContext`]s of this [`StackingContext`].
367    pub(crate) children: Vec<StackingContext>,
368
369    /// The offset of the containing block, used to properly paint child fragments of this
370    /// stacking context or stacking container.
371    pub(crate) containing_block_origin: PhysicalPoint<Au>,
372
373    /// The spatial id of this [`StackingContext`].
374    pub(crate) scroll_tree_node_id: ScrollTreeNodeId,
375
376    /// The clip id of this [`StackingContext`] if it has one.
377    pub(crate) clip_id: ClipId,
378
379    /// The z-index of this [`StackingContext`]. Note that `auto` is represented as 0.
380    pub(crate) z_index: i32,
381
382    /// The text decorations that apply to this [`StackingContext`] propagated via the box tree.
383    #[conditional_malloc_size_of]
384    pub(crate) text_decorations: Rc<Vec<FragmentTextDecoration>>,
385
386    /// If this [`StackingContext`] also created a WebRender reference frame, this field
387    /// holds information about that reference frame.
388    pub(crate) reference_frame_info: Option<StackingContextReferenceFrameInfo>,
389}
390
391impl StackingContext {
392    fn root(scroll_tree_node_id: ScrollTreeNodeId) -> Self {
393        Self {
394            fragment: StackingContextFragments::Root,
395            context_type: StackingContextType::StackingContext,
396            children: Default::default(),
397            containing_block_origin: Default::default(),
398            scroll_tree_node_id,
399            clip_id: ClipId::INVALID,
400            z_index: 0,
401            text_decorations: Default::default(),
402            reference_frame_info: None,
403        }
404    }
405
406    #[expect(clippy::too_many_arguments)]
407    fn create_descendant(
408        &self,
409        context_type: StackingContextType,
410        containing_block_offset: PhysicalPoint<Au>,
411        spatial_id: ScrollTreeNodeId,
412        clip_id: ClipId,
413        initializing_fragment: Arc<BoxFragment>,
414        text_decorations: Rc<Vec<FragmentTextDecoration>>,
415        reference_frame_info: Option<StackingContextReferenceFrameInfo>,
416    ) -> Self {
417        let z_index = initializing_fragment
418            .style()
419            .effective_z_index(initializing_fragment.base.flags);
420        Self {
421            fragment: StackingContextFragments::Fragment(initializing_fragment),
422            context_type,
423            containing_block_origin: containing_block_offset,
424            children: Default::default(),
425            scroll_tree_node_id: spatial_id,
426            clip_id,
427            z_index,
428            text_decorations,
429            reference_frame_info,
430        }
431    }
432
433    pub(crate) fn fragment(&self) -> Option<&Arc<BoxFragment>> {
434        match &self.fragment {
435            StackingContextFragments::Root => None,
436            StackingContextFragments::Fragment(box_fragment) => Some(box_fragment),
437        }
438    }
439
440    fn sort(&mut self) {
441        self.children.sort_by_key(|child| child.z_index)
442    }
443
444    fn print(&self, tree: &mut PrintTree) {
445        let fragment_string = match &self.fragment {
446            StackingContextFragments::Root => "Root".into(),
447            StackingContextFragments::Fragment(box_fragment) => format!(
448                "{:?} rect={:?}",
449                box_fragment.base.tag,
450                box_fragment.content_rect()
451            ),
452        };
453
454        tree.new_level(format!(
455            "{fragment_string} z-index={:?} spatial={:?} clip={:?}",
456            self.z_index, self.scroll_tree_node_id, self.clip_id
457        ));
458
459        for child in self.children.iter() {
460            child.print(tree);
461        }
462
463        tree.end_level();
464    }
465}
466
467#[derive(Clone, Copy, PartialEq)]
468pub(crate) enum StackingContextBuildMode {
469    IncludeHoisted,
470    SkipHoisted,
471}
472
473impl Fragment {
474    pub(crate) fn build_stacking_context_tree(
475        &self,
476        stacking_context_tree: &mut StackingContextTree,
477        containing_block_info: &ContainingBlockInfo,
478        stacking_context: &mut StackingContext,
479        mode: StackingContextBuildMode,
480        text_decorations: &Rc<Vec<FragmentTextDecoration>>,
481    ) {
482        let containing_block = containing_block_info.get_containing_block_for_fragment(self);
483        let cumulative_containing_block = containing_block
484            .rect
485            .translate(containing_block.accumulated_reference_frame_offset);
486        self.set_containing_block(&cumulative_containing_block);
487
488        if self
489            .base()
490            .is_some_and(|base| base.flags.contains(FragmentFlags::IS_COLLAPSED))
491        {
492            return;
493        }
494
495        let fragment_clone = self.clone();
496        match self {
497            Fragment::Box(fragment) | Fragment::Float(fragment) => {
498                if mode == StackingContextBuildMode::SkipHoisted &&
499                    fragment.style().clone_position().is_absolutely_positioned()
500                {
501                    return;
502                }
503
504                let text_decorations = match self {
505                    Fragment::Float(..) => &Default::default(),
506                    _ => text_decorations,
507                };
508
509                fragment.build_stacking_context_tree(
510                    fragment_clone,
511                    stacking_context_tree,
512                    containing_block,
513                    containing_block_info,
514                    stacking_context,
515                    text_decorations,
516                );
517            },
518            Fragment::LayoutRoot(..) => {
519                // These fragments are processed at their originating
520                // `Fragment::AbsoluteOrFixedPositionedPlaceholder` position.
521            },
522            Fragment::AbsoluteOrFixedPositionedPlaceholder(fragment) => {
523                let shared_fragment = fragment.borrow();
524                let fragment_ref = match shared_fragment.fragment.as_ref() {
525                    Some(fragment_ref) => fragment_ref,
526                    None => unreachable!("Found hoisted box with missing fragment."),
527                };
528
529                fragment_ref.build_stacking_context_tree(
530                    stacking_context_tree,
531                    containing_block_info,
532                    stacking_context,
533                    StackingContextBuildMode::IncludeHoisted,
534                    &Default::default(),
535                );
536            },
537            Fragment::Positioning(fragment) => {
538                fragment.build_stacking_context_tree(
539                    stacking_context_tree,
540                    containing_block,
541                    containing_block_info,
542                    stacking_context,
543                    text_decorations,
544                );
545            },
546            Fragment::Text(_) | Fragment::Image(_) | Fragment::IFrame(_) => {},
547        }
548    }
549}
550
551struct ReferenceFrameData {
552    origin: PhysicalPoint<Au>,
553    transform: LayoutTransform,
554    kind: wr::ReferenceFrameKind,
555}
556struct ScrollFrameData {
557    scroll_tree_node_id: ScrollTreeNodeId,
558    scroll_frame_rect: LayoutRect,
559}
560
561struct OverflowFrameData {
562    clip_id: ClipId,
563    scroll_frame_data: Option<ScrollFrameData>,
564}
565
566impl BoxFragment {
567    pub(crate) fn stacking_context_type(&self) -> Option<StackingContextType> {
568        let flags = self.base.flags;
569        let style = self.style();
570        if style.establishes_stacking_context(flags) {
571            return Some(StackingContextType::StackingContext);
572        }
573
574        let box_style = &style.get_box();
575        if box_style.position != ComputedPosition::Static {
576            return Some(StackingContextType::StackingContainer);
577        }
578
579        None
580    }
581
582    fn build_stacking_context_tree(
583        self: &Arc<Self>,
584        fragment: Fragment,
585        stacking_context_tree: &mut StackingContextTree,
586        containing_block: &ContainingBlock,
587        containing_block_info: &ContainingBlockInfo,
588        parent_stacking_context: &mut StackingContext,
589        text_decorations: &Rc<Vec<FragmentTextDecoration>>,
590    ) {
591        self.clear_stacking_context_tree_traversal_data();
592        self.build_stacking_context_tree_maybe_creating_reference_frame(
593            fragment,
594            stacking_context_tree,
595            containing_block,
596            containing_block_info,
597            parent_stacking_context,
598            text_decorations,
599        );
600    }
601
602    fn build_stacking_context_tree_maybe_creating_reference_frame(
603        self: &Arc<Self>,
604        fragment: Fragment,
605        stacking_context_tree: &mut StackingContextTree,
606        containing_block: &ContainingBlock,
607        containing_block_info: &ContainingBlockInfo,
608        parent_stacking_context: &mut StackingContext,
609        text_decorations: &Rc<Vec<FragmentTextDecoration>>,
610    ) {
611        let reference_frame_data =
612            match self.reference_frame_data_if_necessary(&containing_block.rect) {
613                Some(reference_frame_data) => reference_frame_data,
614                None => {
615                    return self.build_stacking_context_tree_maybe_creating_stacking_context(
616                        fragment,
617                        stacking_context_tree,
618                        containing_block,
619                        containing_block_info,
620                        parent_stacking_context,
621                        text_decorations,
622                        None, /* reference_frame_info */
623                    );
624                },
625            };
626
627        // <https://drafts.csswg.org/css-transforms/#transform-function-lists>
628        // > If a transform function causes the current transformation matrix of an object
629        // > to be non-invertible, the object and its content do not get displayed.
630        if !reference_frame_data.transform.is_invertible() {
631            self.clear_stacking_context_tree_traversal_data_recursively();
632            return;
633        }
634
635        let style = self.style();
636        let frame_origin_for_query = self
637            .cumulative_border_box_rect(
638                ContainingBlockCalculation::AlreadyDoneWithStackingContextTree,
639            )
640            .origin
641            .to_webrender();
642        let new_spatial_id = stacking_context_tree.push_reference_frame(
643            reference_frame_data.origin.to_webrender(),
644            frame_origin_for_query,
645            containing_block.scroll_node_id,
646            style.used_transform_style(self.base.flags).to_webrender(),
647            reference_frame_data.transform,
648            reference_frame_data.kind,
649        );
650
651        // WebRender reference frames establish a new coordinate system at their
652        // origin (the border box of the fragment). We need to ensure that any
653        // coordinates we give to WebRender in this reference frame are relative
654        // to the fragment border box. We do this by adjusting the containing
655        // block origin. Note that the `for_absolute_descendants` and
656        // `for_all_absolute_and_fixed_descendants` properties are now bogus,
657        // but all fragments that establish reference frames also establish
658        // containing blocks for absolute and fixed descendants, so those
659        // properties will be replaced before recursing into children.
660        assert!(style.establishes_containing_block_for_all_descendants(self.base.flags));
661        let reference_frame_offset = reference_frame_data.origin.to_vector();
662        let adjusted_containing_block = ContainingBlock::new(
663            containing_block.rect.translate(-reference_frame_offset),
664            new_spatial_id,
665            None,
666            ClipId::INVALID,
667            containing_block.accumulated_reference_frame_offset + reference_frame_offset,
668            false,
669        );
670        let new_containing_block_info =
671            containing_block_info.new_for_non_absolute_descendants(&adjusted_containing_block);
672
673        let reference_frame_info = StackingContextReferenceFrameInfo {
674            parent_spatial_node_id: containing_block.scroll_node_id,
675            captured_clip_id: containing_block.clip_id,
676        };
677
678        self.build_stacking_context_tree_maybe_creating_stacking_context(
679            fragment,
680            stacking_context_tree,
681            &adjusted_containing_block,
682            &new_containing_block_info,
683            parent_stacking_context,
684            text_decorations,
685            Some(reference_frame_info),
686        );
687    }
688
689    #[expect(clippy::too_many_arguments)]
690    fn build_stacking_context_tree_maybe_creating_stacking_context(
691        self: &Arc<Self>,
692        fragment: Fragment,
693        stacking_context_tree: &mut StackingContextTree,
694        containing_block: &ContainingBlock,
695        containing_block_info: &ContainingBlockInfo,
696        parent_stacking_context: &mut StackingContext,
697        text_decorations: &Rc<Vec<FragmentTextDecoration>>,
698        reference_frame_info: Option<StackingContextReferenceFrameInfo>,
699    ) {
700        let with_style = &self.with_style();
701        let style = with_style.style();
702        let Some(stacking_context_type) = self.stacking_context_type() else {
703            with_style.build_stacking_context_tree_for_children(
704                stacking_context_tree,
705                containing_block,
706                containing_block_info,
707                parent_stacking_context,
708                text_decorations,
709            );
710            return;
711        };
712
713        let new_scroll_frame_size = containing_block_info
714            .for_non_absolute_descendants
715            .scroll_frame_size;
716        let spatial_id = self.build_sticky_frame_if_necessary(
717            stacking_context_tree,
718            containing_block.scroll_node_id,
719            &containing_block.rect,
720            &new_scroll_frame_size,
721            containing_block.established_scroll_frame,
722        );
723
724        let clip_id = with_style.build_clip_frame_if_necessary(
725            stacking_context_tree,
726            spatial_id.unwrap_or(containing_block.scroll_node_id),
727            containing_block.clip_id,
728            &containing_block.rect,
729        );
730
731        let clip_id = stacking_context_tree
732            .clip_store
733            .add_for_clip_path(
734                &style.get_svg().clip_path,
735                spatial_id.unwrap_or(containing_block.scroll_node_id),
736                clip_id.unwrap_or(containing_block.clip_id),
737                with_style,
738                containing_block.rect.origin,
739            )
740            .or(clip_id);
741
742        let containing_block = if clip_id.is_some() || spatial_id.is_some() {
743            if let Some(clip_id) = clip_id {
744                self.set_generated_clip_id(clip_id);
745            }
746            if let Some(spatial_id) = spatial_id {
747                self.set_generated_scroll_tree_node_id(spatial_id);
748            }
749            &ContainingBlock {
750                scroll_node_id: spatial_id.unwrap_or(containing_block.scroll_node_id),
751                clip_id: clip_id.unwrap_or(containing_block.clip_id),
752                ..*containing_block
753            }
754        } else {
755            containing_block
756        };
757
758        let box_fragment = fragment
759            .retrieve_box_fragment()
760            .expect("Should never try to make stacking context for non-BoxFragment")
761            .clone();
762        let mut child_stacking_context = parent_stacking_context.create_descendant(
763            stacking_context_type,
764            containing_block.rect.origin,
765            containing_block.scroll_node_id,
766            containing_block.clip_id,
767            box_fragment,
768            text_decorations.clone(),
769            reference_frame_info,
770        );
771        with_style.build_stacking_context_tree_for_children(
772            stacking_context_tree,
773            containing_block,
774            containing_block_info,
775            &mut child_stacking_context,
776            text_decorations,
777        );
778
779        let mut stolen_children = vec![];
780        if stacking_context_type != StackingContextType::StackingContext {
781            stolen_children =
782                std::mem::replace(&mut child_stacking_context.children, stolen_children);
783        } else {
784            child_stacking_context.sort();
785        }
786
787        parent_stacking_context
788            .children
789            .push(child_stacking_context);
790        parent_stacking_context
791            .children
792            .append(&mut stolen_children);
793    }
794}
795
796impl BoxFragmentWithStyle<'_> {
797    fn build_stacking_context_tree_for_children(
798        &self,
799        stacking_context_tree: &mut StackingContextTree,
800        containing_block: &ContainingBlock,
801        containing_block_info: &ContainingBlockInfo,
802        stacking_context: &mut StackingContext,
803        text_decorations: &Rc<Vec<FragmentTextDecoration>>,
804    ) {
805        let style = self.style();
806        let establishes_containing_block_for_all_descendants =
807            style.establishes_containing_block_for_all_descendants(self.base.flags);
808        let establishes_containing_block_for_absolute_descendants =
809            style.establishes_containing_block_for_absolute_descendants(self.base.flags);
810
811        let mut new_scroll_node_id = containing_block.scroll_node_id;
812        self.spatial_tree_node.set(Some(new_scroll_node_id));
813
814        // We want to build the scroll frame after the background and border, because
815        // they shouldn't scroll with the rest of the box content.
816        let mut new_scroll_frame_size = containing_block_info
817            .for_non_absolute_descendants
818            .scroll_frame_size;
819
820        let mut established_scroll_frame = containing_block.established_scroll_frame;
821        // We want to skip leaving scroll frame for anonymous fragments,
822        // because anonymous fragment belong to same node as its closest non-anonymous child.
823        if established_scroll_frame && !self.base.is_anonymous() {
824            established_scroll_frame = false;
825        }
826        let mut new_clip_id = containing_block.clip_id;
827        if let Some(overflow_frame_data) = self.build_overflow_frame_if_necessary(
828            stacking_context_tree,
829            new_scroll_node_id,
830            new_clip_id,
831            &containing_block.rect,
832        ) {
833            new_clip_id = overflow_frame_data.clip_id;
834            self.set_generated_clip_id(new_clip_id);
835
836            if let Some(scroll_frame_data) = overflow_frame_data.scroll_frame_data {
837                new_scroll_node_id = scroll_frame_data.scroll_tree_node_id;
838                new_scroll_frame_size = Some(scroll_frame_data.scroll_frame_rect.size());
839                established_scroll_frame = true;
840                self.set_generated_scroll_tree_node_id(new_scroll_node_id);
841            }
842        }
843
844        let padding_rect = self
845            .padding_rect()
846            .translate(containing_block.rect.origin.to_vector());
847        let content_rect = self
848            .content_rect()
849            .translate(containing_block.rect.origin.to_vector());
850
851        let for_absolute_descendants = ContainingBlock::new(
852            padding_rect,
853            new_scroll_node_id,
854            new_scroll_frame_size,
855            new_clip_id,
856            containing_block.accumulated_reference_frame_offset,
857            established_scroll_frame,
858        );
859        let for_non_absolute_descendants = ContainingBlock::new(
860            content_rect,
861            new_scroll_node_id,
862            new_scroll_frame_size,
863            new_clip_id,
864            containing_block.accumulated_reference_frame_offset,
865            established_scroll_frame,
866        );
867
868        // Create a new `ContainingBlockInfo` for descendants depending on
869        // whether or not this fragment establishes a containing block for
870        // absolute and fixed descendants.
871        let new_containing_block_info = if establishes_containing_block_for_all_descendants {
872            containing_block_info.new_for_absolute_and_fixed_descendants(
873                &for_non_absolute_descendants,
874                &for_absolute_descendants,
875            )
876        } else if establishes_containing_block_for_absolute_descendants {
877            containing_block_info.new_for_absolute_descendants(
878                &for_non_absolute_descendants,
879                &for_absolute_descendants,
880            )
881        } else {
882            containing_block_info.new_for_non_absolute_descendants(&for_non_absolute_descendants)
883        };
884
885        // Text decorations are not propagated to atomic inline-level descendants.
886        // From https://drafts.csswg.org/css2/#lining-striking-props:
887        // > Note that text decorations are not propagated to floating and absolutely
888        // > positioned descendants, nor to the contents of atomic inline-level descendants
889        // > such as inline blocks and inline tables.
890        let text_decorations = match self.is_atomic_inline_level() ||
891            self.base
892                .flags
893                .contains(FragmentFlags::IS_OUTSIDE_LIST_ITEM_MARKER)
894        {
895            true => &Default::default(),
896            false => text_decorations,
897        };
898
899        let new_text_decoration;
900        let text_decorations = match style.clone_text_decoration_line() {
901            TextDecorationLine::NONE => text_decorations,
902            line => {
903                let mut new_vector = (**text_decorations).clone();
904                let color = &style.get_inherited_text().color;
905                new_vector.push(FragmentTextDecoration {
906                    line,
907                    color: style
908                        .clone_text_decoration_color()
909                        .resolve_to_absolute(color),
910                    style: style.clone_text_decoration_style(),
911                    thickness: style.clone_text_decoration_thickness(),
912                });
913                new_text_decoration = Rc::new(new_vector);
914                &new_text_decoration
915            },
916        };
917
918        for child in &self.children {
919            child.build_stacking_context_tree(
920                stacking_context_tree,
921                &new_containing_block_info,
922                stacking_context,
923                StackingContextBuildMode::SkipHoisted,
924                text_decorations,
925            );
926        }
927    }
928
929    fn build_clip_frame_if_necessary(
930        &self,
931        stacking_context_tree: &mut StackingContextTree,
932        parent_scroll_node_id: ScrollTreeNodeId,
933        parent_clip_id: ClipId,
934        containing_block_rect: &PhysicalRect<Au>,
935    ) -> Option<ClipId> {
936        let style = self.style();
937        let position = style.get_box().position;
938        // https://drafts.csswg.org/css2/#clipping
939        // The clip property applies only to absolutely positioned elements
940        if !position.is_absolutely_positioned() {
941            return None;
942        }
943
944        // Only rectangles are supported for now.
945        let clip_rect = match style.get_effects().clip {
946            ClipRectOrAuto::Rect(rect) => rect,
947            _ => return None,
948        };
949
950        let border_rect = self.border_rect();
951        let clip_rect = clip_rect
952            .for_border_rect(border_rect)
953            .translate(containing_block_rect.origin.to_vector())
954            .to_webrender();
955        Some(stacking_context_tree.clip_store.add(
956            BorderRadius::zero(),
957            clip_rect,
958            parent_scroll_node_id,
959            parent_clip_id,
960        ))
961    }
962
963    fn build_overflow_frame_if_necessary(
964        &self,
965        stacking_context_tree: &mut StackingContextTree,
966        parent_scroll_node_id: ScrollTreeNodeId,
967        parent_clip_id: ClipId,
968        containing_block_rect: &PhysicalRect<Au>,
969    ) -> Option<OverflowFrameData> {
970        let style = self.style();
971        let overflow = style.effective_overflow(self.base.flags);
972
973        if overflow.x == ComputedOverflow::Visible && overflow.y == ComputedOverflow::Visible {
974            return None;
975        }
976
977        // Non-scrollable overflow path
978        if overflow.x == ComputedOverflow::Clip || overflow.y == ComputedOverflow::Clip {
979            let overflow_clip_margin = style.get_margin().overflow_clip_margin;
980            let mut overflow_clip_rect = match overflow_clip_margin.visual_box {
981                OverflowClipMarginBox::ContentBox => self.content_rect(),
982                OverflowClipMarginBox::PaddingBox => self.padding_rect(),
983                OverflowClipMarginBox::BorderBox => self.border_rect(),
984            }
985            .translate(containing_block_rect.origin.to_vector())
986            .to_webrender();
987
988            // Adjust by the overflow clip margin.
989            // https://drafts.csswg.org/css-overflow-3/#overflow-clip-margin
990            let clip_margin_offset = overflow_clip_margin.offset.px();
991            overflow_clip_rect = overflow_clip_rect.inflate(clip_margin_offset, clip_margin_offset);
992
993            // The clipping region only gets rounded corners if both axes have `overflow: clip`.
994            // https://drafts.csswg.org/css-overflow-3/#corner-clipping
995            let radii;
996            if overflow.x == ComputedOverflow::Clip && overflow.y == ComputedOverflow::Clip {
997                let builder = BuilderForBoxFragment::new(self, containing_block_rect.origin);
998                let mut offsets_from_border = SideOffsets2D::new_all_same(clip_margin_offset);
999                match overflow_clip_margin.visual_box {
1000                    OverflowClipMarginBox::ContentBox => {
1001                        offsets_from_border -= (self.border + self.padding).to_webrender();
1002                    },
1003                    OverflowClipMarginBox::PaddingBox => {
1004                        offsets_from_border -= self.border.to_webrender();
1005                    },
1006                    OverflowClipMarginBox::BorderBox => {},
1007                };
1008                radii = offset_radii(builder.border_radius(), offsets_from_border);
1009            } else if overflow.x != ComputedOverflow::Clip {
1010                let max = LayoutRect::max_rect();
1011                overflow_clip_rect.min.x = max.min.x;
1012                overflow_clip_rect.max.x = max.max.x;
1013                radii = BorderRadius::zero();
1014            } else {
1015                let max = LayoutRect::max_rect();
1016                overflow_clip_rect.min.y = max.min.y;
1017                overflow_clip_rect.max.y = max.max.y;
1018                radii = BorderRadius::zero();
1019            }
1020
1021            let clip_id = stacking_context_tree.clip_store.add(
1022                radii,
1023                overflow_clip_rect,
1024                parent_scroll_node_id,
1025                parent_clip_id,
1026            );
1027
1028            return Some(OverflowFrameData {
1029                clip_id,
1030                scroll_frame_data: None,
1031            });
1032        }
1033
1034        let scroll_frame_rect = self
1035            .padding_rect()
1036            .translate(containing_block_rect.origin.to_vector())
1037            .to_webrender();
1038
1039        let clip_id = stacking_context_tree.clip_store.add(
1040            BuilderForBoxFragment::new(self, containing_block_rect.origin).border_radius(),
1041            scroll_frame_rect,
1042            parent_scroll_node_id,
1043            parent_clip_id,
1044        );
1045
1046        let tag = self.base.tag?;
1047        let external_scroll_id = wr::ExternalScrollId(
1048            tag.to_display_list_fragment_id(),
1049            stacking_context_tree.paint_info.pipeline_id,
1050        );
1051
1052        let sensitivity = AxesScrollSensitivity {
1053            x: overflow.x.into(),
1054            y: overflow.y.into(),
1055        };
1056
1057        let scroll_tree_node_id = stacking_context_tree.define_scroll_frame(
1058            parent_scroll_node_id,
1059            external_scroll_id,
1060            self.scrollable_overflow().to_webrender(),
1061            scroll_frame_rect,
1062            sensitivity,
1063        );
1064
1065        Some(OverflowFrameData {
1066            clip_id,
1067            scroll_frame_data: Some(ScrollFrameData {
1068                scroll_tree_node_id,
1069                scroll_frame_rect,
1070            }),
1071        })
1072    }
1073}
1074
1075impl BoxFragment {
1076    fn build_sticky_frame_if_necessary(
1077        &self,
1078        stacking_context_tree: &mut StackingContextTree,
1079        parent_scroll_node_id: ScrollTreeNodeId,
1080        containing_block_rect: &PhysicalRect<Au>,
1081        scroll_frame_size: &Option<LayoutSize>,
1082        established_scroll_frame: bool,
1083    ) -> Option<ScrollTreeNodeId> {
1084        let style = self.style();
1085        if style.get_box().position != ComputedPosition::Sticky {
1086            return None;
1087        }
1088
1089        let scroll_frame_size_for_resolve = match scroll_frame_size {
1090            Some(size) => size,
1091            None => {
1092                // This is a direct descendant of a reference frame.
1093                &stacking_context_tree
1094                    .paint_info
1095                    .viewport_details
1096                    .layout_size()
1097            },
1098        };
1099
1100        // Percentages sticky positions offsets are resovled against the size of the
1101        // nearest scroll frame instead of the containing block like for other types
1102        // of positioning.
1103        let scroll_frame_height = Au::from_f32_px(scroll_frame_size_for_resolve.height);
1104        let scroll_frame_width = Au::from_f32_px(scroll_frame_size_for_resolve.width);
1105        let offsets = style.physical_box_offsets();
1106        let offsets = PhysicalSides::<AuOrAuto>::new(
1107            offsets.top.map(|v| v.to_used_value(scroll_frame_height)),
1108            offsets.right.map(|v| v.to_used_value(scroll_frame_width)),
1109            offsets.bottom.map(|v| v.to_used_value(scroll_frame_height)),
1110            offsets.left.map(|v| v.to_used_value(scroll_frame_width)),
1111        );
1112        self.set_resolved_sticky_insets(offsets);
1113
1114        if scroll_frame_size.is_none() {
1115            return None;
1116        }
1117
1118        if offsets.top.is_auto() &&
1119            offsets.right.is_auto() &&
1120            offsets.bottom.is_auto() &&
1121            offsets.left.is_auto()
1122        {
1123            return None;
1124        }
1125
1126        // https://drafts.csswg.org/css-position/#stickypos-insets
1127        // > For each side of the box, if the corresponding inset property is not `auto`, and the
1128        // > corresponding border edge of the box would be outside the corresponding edge of the
1129        // > sticky view rectangle, the box must be visually shifted (as for relative positioning)
1130        // > to be inward of that sticky view rectangle edge, insofar as it can while its position
1131        // > box remains contained within its containing block.
1132        // > The *position box* is its margin box, except that for any side for which the distance
1133        // > between its margin edge and the corresponding edge of its containing block is less
1134        // > than its corresponding margin, that distance is used in place of that margin.
1135        //
1136        // Amendments:
1137        // - Using the "margin edge" seems nonsensical, the spec must mean "border edge" instead:
1138        //   https://github.com/w3c/csswg-drafts/issues/12833
1139        // - `auto` margins need to be treated as zero:
1140        //   https://github.com/w3c/csswg-drafts/issues/12852
1141        //
1142        // We implement this by enforcing a minimum negative offset and a maximum positive offset.
1143        // The logic below is a simplified (but equivalent) version of the description above.
1144        let border_rect = self.border_rect();
1145        let computed_margin = style.physical_margin();
1146        let parent_scroll_node = stacking_context_tree
1147            .paint_info
1148            .scroll_tree
1149            .get_node(parent_scroll_node_id);
1150        let sticky_offset_boundary = match parent_scroll_node.info {
1151            SpatialTreeNodeInfo::Scroll(ref scrollable_node_info) if established_scroll_frame => {
1152                let content_rect = &scrollable_node_info.content_rect;
1153                &PhysicalRect::new(
1154                    PhysicalPoint::new(
1155                        Au::from_f32_px(content_rect.min.x),
1156                        Au::from_f32_px(content_rect.min.y),
1157                    ),
1158                    PhysicalSize::new(
1159                        Au::from_f32_px(content_rect.max.x - content_rect.min.x),
1160                        Au::from_f32_px(content_rect.max.y - content_rect.min.y),
1161                    ),
1162                )
1163            },
1164            _ => containing_block_rect,
1165        };
1166        // Signed distance between each side of the border box to the corresponding side of the
1167        // containing block. Note that |border_rect| is already in the coordinate system of the
1168        // containing block.
1169        let distance_from_border_box_to_cb = PhysicalSides::new(
1170            border_rect.min_y(),
1171            sticky_offset_boundary.width() - border_rect.max_x(),
1172            sticky_offset_boundary.height() - border_rect.max_y(),
1173            border_rect.min_x(),
1174        );
1175        // Shrinks the signed distance by the margin, producing a limit on how much we can shift
1176        // the sticky positioned box without forcing the margin to move outside of the containing
1177        // block.
1178        let offset_bound = |distance, used_margin, computed_margin: LengthPercentageOrAuto| {
1179            let used_margin = if computed_margin.is_auto() {
1180                Au::zero()
1181            } else {
1182                used_margin
1183            };
1184            Au::zero().max(distance - used_margin).to_f32_px()
1185        };
1186
1187        // This is the minimum negative offset and then the maximum positive offset. We specify
1188        // all sides, but they will have no effect if the corresponding inset property is `auto`.
1189        let vertical_offset_bounds = wr::StickyOffsetBounds::new(
1190            -offset_bound(
1191                distance_from_border_box_to_cb.top,
1192                self.margin.top,
1193                computed_margin.top,
1194            ),
1195            offset_bound(
1196                distance_from_border_box_to_cb.bottom,
1197                self.margin.bottom,
1198                computed_margin.bottom,
1199            ),
1200        );
1201        let horizontal_offset_bounds = wr::StickyOffsetBounds::new(
1202            -offset_bound(
1203                distance_from_border_box_to_cb.left,
1204                self.margin.left,
1205                computed_margin.left,
1206            ),
1207            offset_bound(
1208                distance_from_border_box_to_cb.right,
1209                self.margin.right,
1210                computed_margin.right,
1211            ),
1212        );
1213
1214        let frame_rect = border_rect
1215            .translate(containing_block_rect.origin.to_vector())
1216            .to_webrender();
1217
1218        // These are the "margins" between the scrollport and |frame_rect|. They are not the same
1219        // as CSS margins.
1220        let margins = SideOffsets2D::new(
1221            offsets.top.non_auto().map(|v| v.to_f32_px()),
1222            offsets.right.non_auto().map(|v| v.to_f32_px()),
1223            offsets.bottom.non_auto().map(|v| v.to_f32_px()),
1224            offsets.left.non_auto().map(|v| v.to_f32_px()),
1225        );
1226
1227        let sticky_node_id = stacking_context_tree.define_sticky_frame(
1228            parent_scroll_node_id,
1229            frame_rect,
1230            margins,
1231            vertical_offset_bounds,
1232            horizontal_offset_bounds,
1233        );
1234
1235        Some(sticky_node_id)
1236    }
1237
1238    /// Optionally returns the data for building a reference frame, without yet building it.
1239    fn reference_frame_data_if_necessary(
1240        &self,
1241        containing_block_rect: &PhysicalRect<Au>,
1242    ) -> Option<ReferenceFrameData> {
1243        if !self
1244            .style()
1245            .has_effective_transform_or_perspective(self.base.flags)
1246        {
1247            return None;
1248        }
1249
1250        let relative_border_rect = self.border_rect();
1251        let border_rect = relative_border_rect.translate(containing_block_rect.origin.to_vector());
1252        let transform = self.calculate_transform_matrix(&border_rect);
1253        let perspective = self.calculate_perspective_matrix(&border_rect);
1254        let (reference_frame_transform, reference_frame_kind) = match (transform, perspective) {
1255            (None, Some(perspective)) => (
1256                perspective,
1257                wr::ReferenceFrameKind::Perspective {
1258                    scrolling_relative_to: None,
1259                },
1260            ),
1261            (Some(transform), None) => (
1262                transform,
1263                wr::ReferenceFrameKind::Transform {
1264                    is_2d_scale_translation: false,
1265                    should_snap: false,
1266                    paired_with_perspective: false,
1267                },
1268            ),
1269            (Some(transform), Some(perspective)) => (
1270                perspective.then(&transform),
1271                wr::ReferenceFrameKind::Perspective {
1272                    scrolling_relative_to: None,
1273                },
1274            ),
1275            (None, None) => unreachable!(),
1276        };
1277
1278        Some(ReferenceFrameData {
1279            origin: border_rect.origin,
1280            transform: reference_frame_transform,
1281            kind: reference_frame_kind,
1282        })
1283    }
1284
1285    /// Returns the 4D matrix representing this fragment's transform.
1286    pub fn calculate_transform_matrix(
1287        &self,
1288        border_rect: &Rect<Au, CSSPixel>,
1289    ) -> Option<LayoutTransform> {
1290        let style = self.style();
1291        let list = &style.get_box().transform;
1292        let length_rect = au_rect_to_length_rect(border_rect);
1293        // https://drafts.csswg.org/css-transforms-2/#individual-transforms
1294        let rotate = match style.clone_rotate() {
1295            GenericRotate::Rotate(angle) => (0., 0., 1., angle),
1296            GenericRotate::Rotate3D(x, y, z, angle) => {
1297                // These are the raw, unormalized values from CSS, but euclid expects
1298                // rotation input to be normalized, so we must do that first.
1299                get_normalized_vector_and_angle(x, y, z, angle)
1300            },
1301            GenericRotate::None => (0., 0., 1., Angle::zero()),
1302        };
1303        let scale = match style.clone_scale() {
1304            GenericScale::Scale(sx, sy, sz) => (sx, sy, sz),
1305            GenericScale::None => (1., 1., 1.),
1306        };
1307        let translation = match style.clone_translate() {
1308            GenericTranslate::Translate(x, y, z) => LayoutTransform::translation(
1309                x.resolve(length_rect.size.width).px(),
1310                y.resolve(length_rect.size.height).px(),
1311                z.px(),
1312            ),
1313            GenericTranslate::None => LayoutTransform::identity(),
1314        };
1315
1316        let angle = euclid::Angle::radians(rotate.3.radians());
1317        let transform_base = list
1318            .to_transform_3d_matrix(Some(&length_rect.to_untyped()))
1319            .ok()?;
1320        let transform = LayoutTransform::from_untyped(&transform_base.0)
1321            .then_rotate(rotate.0, rotate.1, rotate.2, angle)
1322            .then_scale(scale.0, scale.1, scale.2)
1323            .then(&translation);
1324
1325        let transform_origin = &style.get_box().transform_origin;
1326        let transform_origin_x = transform_origin
1327            .horizontal
1328            .to_used_value(border_rect.size.width)
1329            .to_f32_px();
1330        let transform_origin_y = transform_origin
1331            .vertical
1332            .to_used_value(border_rect.size.height)
1333            .to_f32_px();
1334        let transform_origin_z = transform_origin.depth.px();
1335
1336        Some(transform.change_basis(transform_origin_x, transform_origin_y, transform_origin_z))
1337    }
1338
1339    /// Returns the 4D matrix representing this fragment's perspective.
1340    pub fn calculate_perspective_matrix(
1341        &self,
1342        border_rect: &Rect<Au, CSSPixel>,
1343    ) -> Option<LayoutTransform> {
1344        let style = self.style();
1345        match style.get_box().perspective {
1346            Perspective::Length(length) => {
1347                let perspective_origin = &style.get_box().perspective_origin;
1348                let perspective_origin = LayoutPoint::new(
1349                    perspective_origin
1350                        .horizontal
1351                        .percentage_relative_to(border_rect.size.width.into())
1352                        .px(),
1353                    perspective_origin
1354                        .vertical
1355                        .percentage_relative_to(border_rect.size.height.into())
1356                        .px(),
1357                );
1358
1359                let perspective_matrix = LayoutTransform::from_untyped(
1360                    &transform::create_perspective_matrix(length.px()),
1361                );
1362
1363                Some(perspective_matrix.change_basis(
1364                    perspective_origin.x,
1365                    perspective_origin.y,
1366                    0.0,
1367                ))
1368            },
1369            Perspective::None => None,
1370        }
1371    }
1372
1373    fn clear_stacking_context_tree_traversal_data_recursively(&self) {
1374        fn clear_stacking_context_tree_traversal_data_on_fragments(fragments: &[Fragment]) {
1375            for fragment in fragments.iter() {
1376                match fragment {
1377                    Fragment::LayoutRoot(layout_root_fragment) => layout_root_fragment
1378                        .inner_box_fragment()
1379                        .clear_stacking_context_tree_traversal_data_recursively(),
1380                    Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
1381                        box_fragment.clear_stacking_context_tree_traversal_data_recursively();
1382                    },
1383                    Fragment::Positioning(positioning_fragment) => {
1384                        clear_stacking_context_tree_traversal_data_on_fragments(
1385                            &positioning_fragment.children,
1386                        );
1387                    },
1388                    _ => {},
1389                }
1390            }
1391        }
1392
1393        self.spatial_tree_node.set(None);
1394        self.clear_stacking_context_tree_traversal_data();
1395        clear_stacking_context_tree_traversal_data_on_fragments(&self.children);
1396    }
1397}
1398
1399impl PositioningFragment {
1400    fn build_stacking_context_tree(
1401        &self,
1402        stacking_context_tree: &mut StackingContextTree,
1403        containing_block: &ContainingBlock,
1404        containing_block_info: &ContainingBlockInfo,
1405        stacking_context: &mut StackingContext,
1406        text_decorations: &Rc<Vec<FragmentTextDecoration>>,
1407    ) {
1408        let rect = self
1409            .base
1410            .rect()
1411            .translate(containing_block.rect.origin.to_vector());
1412        let new_containing_block = containing_block.new_replacing_rect(&rect);
1413        let new_containing_block_info =
1414            containing_block_info.new_for_non_absolute_descendants(&new_containing_block);
1415
1416        for child in &self.children {
1417            child.build_stacking_context_tree(
1418                stacking_context_tree,
1419                &new_containing_block_info,
1420                stacking_context,
1421                StackingContextBuildMode::SkipHoisted,
1422                text_decorations,
1423            );
1424        }
1425    }
1426}
1427
1428pub(crate) fn au_rect_to_length_rect(rect: &Rect<Au, CSSPixel>) -> Rect<Length, CSSPixel> {
1429    Rect::new(
1430        Point2D::new(rect.origin.x.into(), rect.origin.y.into()),
1431        Size2D::new(rect.size.width.into(), rect.size.height.into()),
1432    )
1433}