compositing_traits/
display_list.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
5//! Defines data structures which are consumed by the Compositor.
6
7use std::cell::Cell;
8use std::collections::HashMap;
9
10use base::id::ScrollTreeNodeId;
11use base::print_tree::PrintTree;
12use bitflags::bitflags;
13use embedder_traits::ViewportDetails;
14use euclid::SideOffsets2D;
15use fnv::FnvHashMap;
16use malloc_size_of_derive::MallocSizeOf;
17use serde::{Deserialize, Serialize};
18use servo_geometry::FastLayoutTransform;
19use style::values::specified::Overflow;
20use webrender_api::units::{LayoutPixel, LayoutPoint, LayoutRect, LayoutSize, LayoutVector2D};
21use webrender_api::{
22    Epoch, ExternalScrollId, PipelineId, ReferenceFrameKind, ScrollLocation, SpatialId,
23    StickyOffsetBounds, TransformStyle,
24};
25
26/// A scroll type, describing whether what kind of action originated this scroll request.
27/// This is a bitflag as it is also used to track what kinds of [`ScrollType`]s scroll
28/// nodes are sensitive to.
29#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
30pub struct ScrollType(u8);
31
32bitflags! {
33    impl ScrollType: u8 {
34        /// This node can be scrolled by input events or an input event originated this
35        /// scroll.
36        const InputEvents = 1 << 0;
37        /// This node can be scrolled by script events or script originated this scroll.
38        const Script = 1 << 1;
39    }
40}
41
42/// Convert [Overflow] to [ScrollSensitivity].
43impl From<Overflow> for ScrollType {
44    fn from(overflow: Overflow) -> Self {
45        match overflow {
46            Overflow::Hidden => ScrollType::Script,
47            Overflow::Scroll | Overflow::Auto => ScrollType::Script | ScrollType::InputEvents,
48            Overflow::Visible | Overflow::Clip => ScrollType::empty(),
49        }
50    }
51}
52
53/// The [ScrollSensitivity] of particular node in the vertical and horizontal axes.
54#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
55pub struct AxesScrollSensitivity {
56    pub x: ScrollType,
57    pub y: ScrollType,
58}
59
60#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
61pub enum SpatialTreeNodeInfo {
62    ReferenceFrame(ReferenceFrameNodeInfo),
63    Scroll(ScrollableNodeInfo),
64    Sticky(StickyNodeInfo),
65}
66
67#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
68pub struct StickyNodeInfo {
69    pub frame_rect: LayoutRect,
70    pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
71    pub vertical_offset_bounds: StickyOffsetBounds,
72    pub horizontal_offset_bounds: StickyOffsetBounds,
73}
74
75impl StickyNodeInfo {
76    /// Calculate the sticky offset for this [`StickyNodeInfo`] given information about
77    /// sticky positioning from its ancestors.
78    ///
79    /// This is originally taken from WebRender `SpatialTree` implementation.
80    fn calculate_sticky_offset(&self, ancestor_sticky_info: &AncestorStickyInfo) -> LayoutVector2D {
81        let viewport_scroll_offset = &ancestor_sticky_info.nearest_scrolling_ancestor_offset;
82        let viewport_rect = &ancestor_sticky_info.nearest_scrolling_ancestor_viewport;
83
84        if self.margins.top.is_none() &&
85            self.margins.bottom.is_none() &&
86            self.margins.left.is_none() &&
87            self.margins.right.is_none()
88        {
89            return LayoutVector2D::zero();
90        }
91
92        // The viewport and margins of the item establishes the maximum amount that it can
93        // be offset in order to keep it on screen. Since we care about the relationship
94        // between the scrolled content and unscrolled viewport we adjust the viewport's
95        // position by the scroll offset in order to work with their relative positions on the
96        // page.
97        let mut sticky_rect = self.frame_rect.translate(*viewport_scroll_offset);
98
99        let mut sticky_offset = LayoutVector2D::zero();
100        if let Some(margin) = self.margins.top {
101            let top_viewport_edge = viewport_rect.min.y + margin;
102            if sticky_rect.min.y < top_viewport_edge {
103                // If the sticky rect is positioned above the top edge of the viewport (plus margin)
104                // we move it down so that it is fully inside the viewport.
105                sticky_offset.y = top_viewport_edge - sticky_rect.min.y;
106            }
107        }
108
109        // If we don't have a sticky-top offset (sticky_offset.y == 0) then we check for
110        // handling the bottom margin case. Note that the "don't have a sticky-top offset"
111        // case includes the case where we *had* a sticky-top offset but we reduced it to
112        // zero in the above block.
113        if sticky_offset.y <= 0.0 {
114            if let Some(margin) = self.margins.bottom {
115                // If sticky_offset.y is nonzero that means we must have set it
116                // in the sticky-top handling code above, so this item must have
117                // both top and bottom sticky margins. We adjust the item's rect
118                // by the top-sticky offset, and then combine any offset from
119                // the bottom-sticky calculation into sticky_offset below.
120                sticky_rect.min.y += sticky_offset.y;
121                sticky_rect.max.y += sticky_offset.y;
122
123                // Same as the above case, but inverted for bottom-sticky items. Here
124                // we adjust items upwards, resulting in a negative sticky_offset.y,
125                // or reduce the already-present upward adjustment, resulting in a positive
126                // sticky_offset.y.
127                let bottom_viewport_edge = viewport_rect.max.y - margin;
128                if sticky_rect.max.y > bottom_viewport_edge {
129                    sticky_offset.y += bottom_viewport_edge - sticky_rect.max.y;
130                }
131            }
132        }
133
134        // Same as above, but for the x-axis.
135        if let Some(margin) = self.margins.left {
136            let left_viewport_edge = viewport_rect.min.x + margin;
137            if sticky_rect.min.x < left_viewport_edge {
138                sticky_offset.x = left_viewport_edge - sticky_rect.min.x;
139            }
140        }
141
142        if sticky_offset.x <= 0.0 {
143            if let Some(margin) = self.margins.right {
144                sticky_rect.min.x += sticky_offset.x;
145                sticky_rect.max.x += sticky_offset.x;
146                let right_viewport_edge = viewport_rect.max.x - margin;
147                if sticky_rect.max.x > right_viewport_edge {
148                    sticky_offset.x += right_viewport_edge - sticky_rect.max.x;
149                }
150            }
151        }
152
153        // The total "sticky offset" and the extra amount we computed as a result of
154        // scrolling, stored in sticky_offset needs to be clamped to the provided bounds.
155        let clamp =
156            |value: f32, bounds: &StickyOffsetBounds| (value).max(bounds.min).min(bounds.max);
157        sticky_offset.y = clamp(sticky_offset.y, &self.vertical_offset_bounds);
158        sticky_offset.x = clamp(sticky_offset.x, &self.horizontal_offset_bounds);
159
160        sticky_offset
161    }
162}
163
164#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
165pub struct ReferenceFrameNodeInfo {
166    pub origin: LayoutPoint,
167    /// Origin of this frame relative to the document for bounding box queries.
168    pub frame_origin_for_query: LayoutPoint,
169    pub transform_style: TransformStyle,
170    pub transform: FastLayoutTransform,
171    pub kind: ReferenceFrameKind,
172}
173
174/// Data stored for nodes in the [ScrollTree] that actually scroll,
175/// as opposed to reference frames and sticky nodes which do not.
176#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
177pub struct ScrollableNodeInfo {
178    /// The external scroll id of this node, used to track
179    /// it between successive re-layouts.
180    pub external_id: ExternalScrollId,
181
182    /// The content rectangle for this scroll node;
183    pub content_rect: LayoutRect,
184
185    /// The clip rectange for this scroll node.
186    pub clip_rect: LayoutRect,
187
188    /// Whether this `ScrollableNode` is sensitive to input events.
189    pub scroll_sensitivity: AxesScrollSensitivity,
190
191    /// The current offset of this scroll node.
192    pub offset: LayoutVector2D,
193
194    /// Whether or not the scroll offset of this node has changed and it needs it's
195    /// cached transformations invalidated.
196    pub offset_changed: Cell<bool>,
197}
198
199impl ScrollableNodeInfo {
200    fn scroll_to_offset(
201        &mut self,
202        new_offset: LayoutVector2D,
203        context: ScrollType,
204    ) -> Option<LayoutVector2D> {
205        if !self.scroll_sensitivity.x.contains(context) &&
206            !self.scroll_sensitivity.y.contains(context)
207        {
208            return None;
209        }
210
211        let scrollable_size = self.scrollable_size();
212        let original_layer_scroll_offset = self.offset;
213
214        if scrollable_size.width > 0. && self.scroll_sensitivity.x.contains(context) {
215            self.offset.x = new_offset.x.clamp(0.0, scrollable_size.width);
216        }
217
218        if scrollable_size.height > 0. && self.scroll_sensitivity.y.contains(context) {
219            self.offset.y = new_offset.y.clamp(0.0, scrollable_size.height);
220        }
221
222        if self.offset != original_layer_scroll_offset {
223            self.offset_changed.set(true);
224            Some(self.offset)
225        } else {
226            None
227        }
228    }
229
230    fn scroll_to_webrender_location(
231        &mut self,
232        scroll_location: ScrollLocation,
233        context: ScrollType,
234    ) -> Option<LayoutVector2D> {
235        if !self.scroll_sensitivity.x.contains(context) &&
236            !self.scroll_sensitivity.y.contains(context)
237        {
238            return None;
239        }
240
241        let delta = match scroll_location {
242            ScrollLocation::Delta(delta) => delta,
243            ScrollLocation::Start => {
244                if self.offset.y.round() <= 0.0 {
245                    // Nothing to do on this layer.
246                    return None;
247                }
248
249                self.offset.y = 0.0;
250                self.offset_changed.set(true);
251                return Some(self.offset);
252            },
253            ScrollLocation::End => {
254                let end_pos = self.scrollable_size().height;
255                if self.offset.y.round() >= end_pos {
256                    // Nothing to do on this layer.
257                    return None;
258                }
259
260                self.offset.y = end_pos;
261                self.offset_changed.set(true);
262                return Some(self.offset);
263            },
264        };
265
266        self.scroll_to_offset(self.offset + delta, context)
267    }
268}
269
270impl ScrollableNodeInfo {
271    fn scrollable_size(&self) -> LayoutSize {
272        self.content_rect.size() - self.clip_rect.size()
273    }
274}
275
276/// A cached of transforms of a particular [`ScrollTree`] node in both directions:
277/// mapping from node-relative points to root-relative points and vice-versa.
278///
279/// Potential ideas for improvement:
280///  - Test optimizing simple translations to avoid having to do full matrix
281///    multiplication when transforms are not involved.
282#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, Serialize)]
283pub struct ScrollTreeNodeTransformationCache {
284    node_to_root_transform: FastLayoutTransform,
285    root_to_node_transform: Option<FastLayoutTransform>,
286}
287
288#[derive(Default)]
289struct AncestorStickyInfo {
290    nearest_scrolling_ancestor_offset: LayoutVector2D,
291    nearest_scrolling_ancestor_viewport: LayoutRect,
292}
293
294#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
295/// A node in a tree of scroll nodes. This may either be a scrollable
296/// node which responds to scroll events or a non-scrollable one.
297pub struct ScrollTreeNode {
298    /// The index of the parent of this node in the tree. If this is
299    /// None then this is the root node.
300    pub parent: Option<ScrollTreeNodeId>,
301
302    /// The children of this [`ScrollTreeNode`].
303    pub children: Vec<ScrollTreeNodeId>,
304
305    /// The WebRender id, which is filled in when this tree is serialiezd
306    /// into a WebRender display list.
307    pub webrender_id: Option<SpatialId>,
308
309    /// Specific information about this node, depending on whether it is a scroll node
310    /// or a reference frame.
311    pub info: SpatialTreeNodeInfo,
312
313    /// Cached transformation information that's used to do things like hit testing
314    /// and viewport bounding box calculation.
315    transformation_cache: Cell<Option<ScrollTreeNodeTransformationCache>>,
316}
317
318impl ScrollTreeNode {
319    /// Get the WebRender [`SpatialId`] for the given [`ScrollNodeId`]. This will
320    /// panic if [`ScrollTree::build_display_list`] has not been called yet.
321    pub fn webrender_id(&self) -> SpatialId {
322        self.webrender_id
323            .expect("Should have called ScrollTree::build_display_list before querying SpatialId")
324    }
325
326    /// Get the external id of this node.
327    pub fn external_id(&self) -> Option<ExternalScrollId> {
328        match self.info {
329            SpatialTreeNodeInfo::Scroll(ref info) => Some(info.external_id),
330            _ => None,
331        }
332    }
333
334    /// Get the offset id of this node if it applies.
335    pub fn offset(&self) -> Option<LayoutVector2D> {
336        match self.info {
337            SpatialTreeNodeInfo::Scroll(ref info) => Some(info.offset),
338            _ => None,
339        }
340    }
341
342    /// Scroll this node given a WebRender ScrollLocation. Returns a tuple that can
343    /// be used to scroll an individual WebRender scroll frame if the operation
344    /// actually changed an offset.
345    fn scroll(
346        &mut self,
347        scroll_location: ScrollLocation,
348        context: ScrollType,
349    ) -> Option<(ExternalScrollId, LayoutVector2D)> {
350        let SpatialTreeNodeInfo::Scroll(ref mut info) = self.info else {
351            return None;
352        };
353
354        info.scroll_to_webrender_location(scroll_location, context)
355            .map(|location| (info.external_id, location))
356    }
357
358    pub fn debug_print(&self, print_tree: &mut PrintTree, node_index: usize) {
359        match &self.info {
360            SpatialTreeNodeInfo::ReferenceFrame(info) => {
361                print_tree.new_level(format!(
362                    "Reference Frame({node_index}): webrender_id={:?}\
363                        \norigin: {:?}\
364                        \ntransform_style: {:?}\
365                        \ntransform: {:?}\
366                        \nkind: {:?}",
367                    self.webrender_id, info.origin, info.transform_style, info.transform, info.kind,
368                ));
369            },
370            SpatialTreeNodeInfo::Scroll(info) => {
371                print_tree.new_level(format!(
372                    "Scroll Frame({node_index}): webrender_id={:?}\
373                        \nexternal_id: {:?}\
374                        \ncontent_rect: {:?}\
375                        \nclip_rect: {:?}\
376                        \nscroll_sensitivity: {:?}\
377                        \noffset: {:?}",
378                    self.webrender_id,
379                    info.external_id,
380                    info.content_rect,
381                    info.clip_rect,
382                    info.scroll_sensitivity,
383                    info.offset,
384                ));
385            },
386            SpatialTreeNodeInfo::Sticky(info) => {
387                print_tree.new_level(format!(
388                    "Sticky Frame({node_index}): webrender_id={:?}\
389                        \nframe_rect: {:?}\
390                        \nmargins: {:?}\
391                        \nhorizontal_offset_bounds: {:?}\
392                        \nvertical_offset_bounds: {:?}",
393                    self.webrender_id,
394                    info.frame_rect,
395                    info.margins,
396                    info.horizontal_offset_bounds,
397                    info.vertical_offset_bounds,
398                ));
399            },
400        };
401    }
402
403    fn invalidate_cached_transforms(&self, scroll_tree: &ScrollTree, ancestors_invalid: bool) {
404        let node_invalid = match &self.info {
405            SpatialTreeNodeInfo::Scroll(info) => info.offset_changed.take(),
406            _ => false,
407        };
408
409        let invalid = node_invalid || ancestors_invalid;
410        if invalid {
411            self.transformation_cache.set(None);
412        }
413
414        for child_id in &self.children {
415            scroll_tree
416                .get_node(child_id)
417                .invalidate_cached_transforms(scroll_tree, invalid);
418        }
419    }
420}
421
422/// A tree of spatial nodes, which mirrors the spatial nodes in the WebRender
423/// display list, except these are used to scrolling in the compositor so that
424/// new offsets can be sent to WebRender.
425#[derive(Debug, Default, Deserialize, MallocSizeOf, Serialize)]
426pub struct ScrollTree {
427    /// A list of compositor-side scroll nodes that describe the tree
428    /// of WebRender spatial nodes, used by the compositor to scroll the
429    /// contents of the display list.
430    pub nodes: Vec<ScrollTreeNode>,
431}
432
433impl ScrollTree {
434    /// Add a scroll node to this ScrollTree returning the id of the new node.
435    pub fn add_scroll_tree_node(
436        &mut self,
437        parent: Option<&ScrollTreeNodeId>,
438        info: SpatialTreeNodeInfo,
439    ) -> ScrollTreeNodeId {
440        self.nodes.push(ScrollTreeNode {
441            parent: parent.cloned(),
442            children: Vec::new(),
443            webrender_id: None,
444            info,
445            transformation_cache: Cell::default(),
446        });
447
448        let new_node_id = ScrollTreeNodeId {
449            index: self.nodes.len() - 1,
450        };
451
452        if let Some(parent_id) = parent {
453            self.get_node_mut(parent_id).children.push(new_node_id);
454        }
455
456        new_node_id
457    }
458
459    /// Once WebRender display list construction is complete for this [`ScrollTree`], update
460    /// the mapping of nodes to WebRender [`SpatialId`]s.
461    pub fn update_mapping(&mut self, mapping: Vec<SpatialId>) {
462        for (spatial_id, node) in mapping.into_iter().zip(self.nodes.iter_mut()) {
463            node.webrender_id = Some(spatial_id);
464        }
465    }
466
467    /// Get a mutable reference to the node with the given index.
468    pub fn get_node_mut(&mut self, id: &ScrollTreeNodeId) -> &mut ScrollTreeNode {
469        &mut self.nodes[id.index]
470    }
471
472    /// Get an immutable reference to the node with the given index.
473    pub fn get_node(&self, id: &ScrollTreeNodeId) -> &ScrollTreeNode {
474        &self.nodes[id.index]
475    }
476
477    /// Get the WebRender [`SpatialId`] for the given [`ScrollNodeId`]. This will
478    /// panic if [`ScrollTree::build_display_list`] has not been called yet.
479    pub fn webrender_id(&self, id: &ScrollTreeNodeId) -> SpatialId {
480        self.get_node(id).webrender_id()
481    }
482
483    pub fn scroll_node_or_ancestor_inner(
484        &mut self,
485        scroll_node_id: &ScrollTreeNodeId,
486        scroll_location: ScrollLocation,
487        context: ScrollType,
488    ) -> Option<(ExternalScrollId, LayoutVector2D)> {
489        let parent = {
490            let node = &mut self.get_node_mut(scroll_node_id);
491            let result = node.scroll(scroll_location, context);
492            if result.is_some() {
493                return result;
494            }
495            node.parent
496        };
497
498        parent.and_then(|parent| {
499            self.scroll_node_or_ancestor_inner(&parent, scroll_location, context)
500        })
501    }
502
503    fn node_with_external_scroll_node_id(
504        &self,
505        external_id: &ExternalScrollId,
506    ) -> Option<ScrollTreeNodeId> {
507        self.nodes
508            .iter()
509            .enumerate()
510            .find_map(|(index, node)| match &node.info {
511                SpatialTreeNodeInfo::Scroll(info) if info.external_id == *external_id => {
512                    Some(ScrollTreeNodeId { index })
513                },
514                _ => None,
515            })
516    }
517
518    /// Scroll the scroll node with the given [`ExternalScrollId`] on this scroll tree. If
519    /// the node cannot be scrolled, because it's already scrolled to the maximum scroll
520    /// extent, try to scroll an ancestor of this node. Returns the node scrolled and the
521    /// new offset if a scroll was performed, otherwise returns None.
522    pub fn scroll_node_or_ancestor(
523        &mut self,
524        external_id: &ExternalScrollId,
525        scroll_location: ScrollLocation,
526        context: ScrollType,
527    ) -> Option<(ExternalScrollId, LayoutVector2D)> {
528        let scroll_node_id = self.node_with_external_scroll_node_id(external_id)?;
529        let result = self.scroll_node_or_ancestor_inner(&scroll_node_id, scroll_location, context);
530        if result.is_some() {
531            self.invalidate_cached_transforms();
532        }
533        result
534    }
535
536    /// Given an [`ExternalScrollId`] and an offset, update the scroll offset of the scroll node
537    /// with the given id.
538    pub fn set_scroll_offset_for_node_with_external_scroll_id(
539        &mut self,
540        external_scroll_id: ExternalScrollId,
541        offset: LayoutVector2D,
542        context: ScrollType,
543    ) -> Option<LayoutVector2D> {
544        let result = self.nodes.iter_mut().find_map(|node| match node.info {
545            SpatialTreeNodeInfo::Scroll(ref mut scroll_info)
546                if scroll_info.external_id == external_scroll_id =>
547            {
548                scroll_info.scroll_to_offset(offset, context)
549            },
550            _ => None,
551        });
552
553        if result.is_some() {
554            self.invalidate_cached_transforms();
555        }
556
557        result
558    }
559
560    /// Given a set of all scroll offsets coming from the Servo renderer, update all of the offsets
561    /// for nodes that actually exist in this tree.
562    pub fn set_all_scroll_offsets(
563        &mut self,
564        offsets: &FnvHashMap<ExternalScrollId, LayoutVector2D>,
565    ) {
566        for node in self.nodes.iter_mut() {
567            if let SpatialTreeNodeInfo::Scroll(ref mut scroll_info) = node.info {
568                if let Some(offset) = offsets.get(&scroll_info.external_id) {
569                    scroll_info.scroll_to_offset(*offset, ScrollType::Script);
570                }
571            }
572        }
573
574        self.invalidate_cached_transforms();
575    }
576
577    /// Set the offsets of all scrolling nodes in this tree to 0.
578    pub fn reset_all_scroll_offsets(&mut self) {
579        for node in self.nodes.iter_mut() {
580            if let SpatialTreeNodeInfo::Scroll(ref mut scroll_info) = node.info {
581                scroll_info.scroll_to_offset(LayoutVector2D::zero(), ScrollType::Script);
582            }
583        }
584
585        self.invalidate_cached_transforms();
586    }
587
588    /// Collect all of the scroll offsets of the scrolling nodes of this tree into a
589    /// [`HashMap`] which can be applied to another tree.
590    pub fn scroll_offsets(&self) -> FnvHashMap<ExternalScrollId, LayoutVector2D> {
591        HashMap::from_iter(self.nodes.iter().filter_map(|node| match node.info {
592            SpatialTreeNodeInfo::Scroll(ref scroll_info) => {
593                Some((scroll_info.external_id, scroll_info.offset))
594            },
595            _ => None,
596        }))
597    }
598
599    /// Get the scroll offset for the given [`ExternalScrollId`] or `None` if that node cannot
600    /// be found in the tree.
601    pub fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D> {
602        self.nodes.iter().find_map(|node| match node.info {
603            SpatialTreeNodeInfo::Scroll(ref info) if info.external_id == id => Some(info.offset),
604            _ => None,
605        })
606    }
607
608    /// Find a transformation that can convert a point in the node coordinate system to a
609    /// point in the root coordinate system.
610    pub fn cumulative_node_to_root_transform(
611        &self,
612        node_id: &ScrollTreeNodeId,
613    ) -> FastLayoutTransform {
614        let node = self.get_node(node_id);
615        if let Some(cached_transforms) = node.transformation_cache.get() {
616            return cached_transforms.node_to_root_transform;
617        }
618
619        let (transforms, _) = self.cumulative_node_transform_inner(node);
620        node.transformation_cache.set(Some(transforms));
621        transforms.node_to_root_transform
622    }
623
624    /// Find a transformation that can convert a point in the root coordinate system to a
625    /// point in the coordinate system of the given node. This may be `None` if the cumulative
626    /// transform is uninvertible.
627    pub fn cumulative_root_to_node_transform(
628        &self,
629        node_id: &ScrollTreeNodeId,
630    ) -> Option<FastLayoutTransform> {
631        let node = self.get_node(node_id);
632        if let Some(cached_transforms) = node.transformation_cache.get() {
633            return cached_transforms.root_to_node_transform;
634        }
635
636        let (transforms, _) = self.cumulative_node_transform_inner(node);
637        node.transformation_cache.set(Some(transforms));
638        transforms.root_to_node_transform
639    }
640
641    /// Traverse a scroll node to its root to calculate the transform.
642    fn cumulative_node_transform_inner(
643        &self,
644        node: &ScrollTreeNode,
645    ) -> (ScrollTreeNodeTransformationCache, AncestorStickyInfo) {
646        let (parent_transforms, mut sticky_info) = match node.parent {
647            Some(parent_id) => self.cumulative_node_transform_inner(self.get_node(&parent_id)),
648            None => (Default::default(), Default::default()),
649        };
650
651        let (node_to_parent_transform, parent_to_node_transform) = match &node.info {
652            SpatialTreeNodeInfo::ReferenceFrame(info) => {
653                // To apply a transformation we need to make sure the rectangle's
654                // coordinate space is the same as reference frame's coordinate space.
655                let offset = info.frame_origin_for_query.to_vector();
656                let node_to_parent_transform =
657                    info.transform.pre_translate(-offset).then_translate(offset);
658
659                let parent_to_node_transform =
660                    FastLayoutTransform::Offset(-info.origin.to_vector());
661                let parent_to_node_transform = info
662                    .transform
663                    .inverse()
664                    .map(|inverse_transform| parent_to_node_transform.then(&inverse_transform));
665
666                sticky_info.nearest_scrolling_ancestor_viewport = sticky_info
667                    .nearest_scrolling_ancestor_viewport
668                    .translate(-info.origin.to_vector());
669
670                (node_to_parent_transform, parent_to_node_transform)
671            },
672            SpatialTreeNodeInfo::Scroll(info) => {
673                sticky_info.nearest_scrolling_ancestor_viewport = info.clip_rect;
674                sticky_info.nearest_scrolling_ancestor_offset = -info.offset;
675                let offset_transform = FastLayoutTransform::Offset(-info.offset);
676                (offset_transform, offset_transform.inverse())
677            },
678
679            SpatialTreeNodeInfo::Sticky(info) => {
680                let offset = info.calculate_sticky_offset(&sticky_info);
681                sticky_info.nearest_scrolling_ancestor_offset += offset;
682                let offset_transform = FastLayoutTransform::Offset(offset);
683                (offset_transform, offset_transform.inverse())
684            },
685        };
686
687        let node_to_root_transform =
688            node_to_parent_transform.then(&parent_transforms.node_to_root_transform);
689        let root_to_node_transform = parent_to_node_transform.map(|parent_to_node_transform| {
690            parent_transforms
691                .root_to_node_transform
692                .map_or(parent_to_node_transform, |parent_transform| {
693                    parent_transform.then(&parent_to_node_transform)
694                })
695        });
696
697        let transforms = ScrollTreeNodeTransformationCache {
698            node_to_root_transform,
699            root_to_node_transform,
700        };
701        (transforms, sticky_info)
702    }
703
704    fn invalidate_cached_transforms(&self) {
705        let Some(root_node) = self.nodes.first() else {
706            return;
707        };
708        root_node.invalidate_cached_transforms(self, false /* ancestors_invalid */);
709    }
710
711    fn external_scroll_id_for_scroll_tree_node(
712        &self,
713        id: ScrollTreeNodeId,
714    ) -> Option<ExternalScrollId> {
715        let mut maybe_node = Some(self.get_node(&id));
716
717        while let Some(node) = maybe_node {
718            if let Some(external_scroll_id) = node.external_id() {
719                return Some(external_scroll_id);
720            }
721            maybe_node = node.parent.map(|id| self.get_node(&id));
722        }
723
724        None
725    }
726}
727
728/// In order to pretty print the [ScrollTree] structure, we are converting
729/// the node list inside the tree to be a adjacency list. The adjacency list
730/// then is used for the [ScrollTree::debug_print_traversal] of the tree.
731///
732/// This preprocessing helps decouples print logic a lot from its construction.
733type AdjacencyListForPrint = Vec<Vec<ScrollTreeNodeId>>;
734
735/// Implementation of [ScrollTree] that is related to debugging.
736// FIXME: probably we could have a universal trait for this. Especially for
737//        structures that utilizes PrintTree.
738impl ScrollTree {
739    fn nodes_in_adjacency_list(&self) -> AdjacencyListForPrint {
740        let mut adjacency_list: AdjacencyListForPrint = vec![Default::default(); self.nodes.len()];
741
742        for (node_index, node) in self.nodes.iter().enumerate() {
743            let current_id = ScrollTreeNodeId { index: node_index };
744            if let Some(parent_id) = node.parent {
745                adjacency_list[parent_id.index].push(current_id);
746            }
747        }
748
749        adjacency_list
750    }
751
752    fn debug_print_traversal(
753        &self,
754        print_tree: &mut PrintTree,
755        current_id: &ScrollTreeNodeId,
756        adjacency_list: &[Vec<ScrollTreeNodeId>],
757    ) {
758        for node_id in &adjacency_list[current_id.index] {
759            self.nodes[node_id.index].debug_print(print_tree, node_id.index);
760            self.debug_print_traversal(print_tree, node_id, adjacency_list);
761        }
762        print_tree.end_level();
763    }
764
765    /// Print the [ScrollTree]. Particularly, we are printing the node in
766    /// preorder traversal. The order of the nodes will depends of the
767    /// index of a node in the [ScrollTree] which corresponds to the
768    /// declarations of the nodes.
769    // TODO(stevennovaryo): add information about which fragment that
770    //                      defines this node.
771    pub fn debug_print(&self) {
772        let mut print_tree = PrintTree::new("Scroll Tree".to_owned());
773
774        let adj_list = self.nodes_in_adjacency_list();
775        let root_id = ScrollTreeNodeId { index: 0 };
776
777        self.nodes[root_id.index].debug_print(&mut print_tree, root_id.index);
778        self.debug_print_traversal(&mut print_tree, &root_id, &adj_list);
779        print_tree.end_level();
780    }
781}
782
783/// A data structure which stores compositor-side information about
784/// display lists sent to the compositor.
785#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
786pub struct CompositorDisplayListInfo {
787    /// The WebRender [PipelineId] of this display list.
788    pub pipeline_id: PipelineId,
789
790    /// The [`ViewportDetails`] that describe the viewport in the script/layout thread at
791    /// the time this display list was created.
792    pub viewport_details: ViewportDetails,
793
794    /// The size of this display list's content.
795    pub content_size: LayoutSize,
796
797    /// The epoch of the display list.
798    pub epoch: Epoch,
799
800    /// A ScrollTree used by the compositor to scroll the contents of the
801    /// display list.
802    pub scroll_tree: ScrollTree,
803
804    /// The `ScrollTreeNodeId` of the root reference frame of this info's scroll
805    /// tree.
806    pub root_reference_frame_id: ScrollTreeNodeId,
807
808    /// The `ScrollTreeNodeId` of the topmost scrolling frame of this info's scroll
809    /// tree.
810    pub root_scroll_node_id: ScrollTreeNodeId,
811
812    /// Contentful paint i.e. whether the display list contains items of type
813    /// text, image, non-white canvas or SVG). Used by metrics.
814    /// See <https://w3c.github.io/paint-timing/#first-contentful-paint>.
815    pub is_contentful: bool,
816
817    /// Whether the first layout or a subsequent (incremental) layout triggered this
818    /// display list creation.
819    pub first_reflow: bool,
820}
821
822impl CompositorDisplayListInfo {
823    /// Create a new CompositorDisplayListInfo with the root reference frame
824    /// and scroll frame already added to the scroll tree.
825    pub fn new(
826        viewport_details: ViewportDetails,
827        content_size: LayoutSize,
828        pipeline_id: PipelineId,
829        epoch: Epoch,
830        viewport_scroll_sensitivity: AxesScrollSensitivity,
831        first_reflow: bool,
832    ) -> Self {
833        let mut scroll_tree = ScrollTree::default();
834        let root_reference_frame_id = scroll_tree.add_scroll_tree_node(
835            None,
836            SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo {
837                origin: Default::default(),
838                frame_origin_for_query: Default::default(),
839                transform_style: TransformStyle::Flat,
840                transform: FastLayoutTransform::identity(),
841                kind: ReferenceFrameKind::default(),
842            }),
843        );
844        let root_scroll_node_id = scroll_tree.add_scroll_tree_node(
845            Some(&root_reference_frame_id),
846            SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo {
847                external_id: ExternalScrollId(0, pipeline_id),
848                content_rect: LayoutRect::from_origin_and_size(LayoutPoint::zero(), content_size),
849                clip_rect: LayoutRect::from_origin_and_size(
850                    LayoutPoint::zero(),
851                    viewport_details.layout_size(),
852                ),
853                scroll_sensitivity: viewport_scroll_sensitivity,
854                offset: LayoutVector2D::zero(),
855                offset_changed: Cell::new(false),
856            }),
857        );
858
859        CompositorDisplayListInfo {
860            pipeline_id,
861            viewport_details,
862            content_size,
863            epoch,
864            scroll_tree,
865            root_reference_frame_id,
866            root_scroll_node_id,
867            is_contentful: false,
868            first_reflow,
869        }
870    }
871
872    pub fn external_scroll_id_for_scroll_tree_node(
873        &self,
874        id: ScrollTreeNodeId,
875    ) -> ExternalScrollId {
876        self.scroll_tree
877            .external_scroll_id_for_scroll_tree_node(id)
878            .unwrap_or(ExternalScrollId(0, self.pipeline_id))
879    }
880}