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