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