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