Skip to main content

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