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