webrender/
spatial_tree.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 http://mozilla.org/MPL/2.0/. */
4
5use api::{ExternalScrollId, PropertyBinding, ReferenceFrameKind, TransformStyle, PropertyBindingId};
6use api::{APZScrollGeneration, HasScrollLinkedEffect, PipelineId, SampledScrollOffset, SpatialTreeItemKey};
7use api::units::*;
8use euclid::Transform3D;
9use crate::gpu_types::TransformPalette;
10use crate::internal_types::{FastHashMap, FastHashSet, FrameMemory, PipelineInstanceId};
11use crate::print_tree::{PrintableTree, PrintTree, PrintTreePrinter};
12use crate::scene::SceneProperties;
13use crate::spatial_node::{ReferenceFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo, SpatialNodeDescriptor};
14use crate::spatial_node::{SpatialNodeUid, ScrollFrameKind, SceneSpatialNode, SpatialNodeInfo, SpatialNodeUidKind};
15use std::{ops, u32};
16use crate::util::{FastTransform, LayoutToWorldFastTransform, MatrixHelpers, ScaleOffset, scale_factors};
17use smallvec::SmallVec;
18use std::collections::hash_map::Entry;
19use crate::util::TransformedRectKind;
20use peek_poke::PeekPoke;
21
22
23/// An id that identifies coordinate systems in the SpatialTree. Each
24/// coordinate system has an id and those ids will be shared when the coordinates
25/// system are the same or are in the same axis-aligned space. This allows
26/// for optimizing mask generation.
27#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
28#[cfg_attr(feature = "capture", derive(Serialize))]
29#[cfg_attr(feature = "replay", derive(Deserialize))]
30pub struct CoordinateSystemId(pub u32);
31
32/// A node in the hierarchy of coordinate system
33/// transforms.
34#[derive(Debug)]
35#[cfg_attr(feature = "capture", derive(Serialize))]
36#[cfg_attr(feature = "replay", derive(Deserialize))]
37pub struct CoordinateSystem {
38    pub transform: LayoutTransform,
39    pub world_transform: LayoutToWorldTransform,
40    pub should_flatten: bool,
41    pub parent: Option<CoordinateSystemId>,
42}
43
44impl CoordinateSystem {
45    fn root() -> Self {
46        CoordinateSystem {
47            transform: LayoutTransform::identity(),
48            world_transform: LayoutToWorldTransform::identity(),
49            should_flatten: false,
50            parent: None,
51        }
52    }
53}
54
55#[derive(Debug, Copy, Clone, Eq, Hash, MallocSizeOf, PartialEq, PeekPoke, Default)]
56#[cfg_attr(feature = "capture", derive(Serialize))]
57#[cfg_attr(feature = "replay", derive(Deserialize))]
58pub struct SpatialNodeIndex(pub u32);
59
60impl SpatialNodeIndex {
61    pub const INVALID: SpatialNodeIndex = SpatialNodeIndex(u32::MAX);
62
63    /// May be set on a cluster / picture during scene building if the spatial
64    /// node is not known at this time. It must be set to a valid value before
65    /// scene building is complete (by `finalize_picture`). In future, we could
66    /// make this type-safe with a wrapper type to ensure we know when a spatial
67    /// node index may have an unknown value.
68    pub const UNKNOWN: SpatialNodeIndex = SpatialNodeIndex(u32::MAX - 1);
69}
70
71// In some cases, the conversion from CSS pixels to device pixels can result in small
72// rounding errors when calculating the scrollable distance of a scroll frame. Apply
73// a small epsilon so that we don't detect these frames as "real" scroll frames.
74const MIN_SCROLLABLE_AMOUNT: f32 = 0.01;
75
76// The minimum size for a scroll frame for it to be considered for a scroll root.
77const MIN_SCROLL_ROOT_SIZE: f32 = 128.0;
78
79impl SpatialNodeIndex {
80    pub fn new(index: usize) -> Self {
81        debug_assert!(index < ::std::u32::MAX as usize);
82        SpatialNodeIndex(index as u32)
83    }
84}
85
86impl CoordinateSystemId {
87    pub fn root() -> Self {
88        CoordinateSystemId(0)
89    }
90}
91
92#[derive(Debug, Copy, Clone, PartialEq)]
93pub enum VisibleFace {
94    Front,
95    Back,
96}
97
98impl Default for VisibleFace {
99    fn default() -> Self {
100        VisibleFace::Front
101    }
102}
103
104impl ops::Not for VisibleFace {
105    type Output = Self;
106    fn not(self) -> Self {
107        match self {
108            VisibleFace::Front => VisibleFace::Back,
109            VisibleFace::Back => VisibleFace::Front,
110        }
111    }
112}
113
114/// Allows functions and methods to retrieve common information about
115/// a spatial node, whether during scene or frame building
116pub trait SpatialNodeContainer {
117    /// Get the common information for a given spatial node
118    fn get_node_info(&self, index: SpatialNodeIndex) -> SpatialNodeInfo;
119}
120
121#[cfg_attr(feature = "capture", derive(Serialize))]
122#[cfg_attr(feature = "replay", derive(Deserialize))]
123enum StoreElement<T> {
124    Empty,
125    Occupied(T),
126}
127
128#[cfg_attr(feature = "capture", derive(Serialize))]
129#[cfg_attr(feature = "replay", derive(Deserialize))]
130struct Store<T> {
131    elements: Vec<StoreElement<T>>,
132    free_indices: Vec<usize>,
133}
134
135impl<T> Store<T> {
136    fn new() -> Self {
137        Store {
138            elements: Vec::new(),
139            free_indices: Vec::new(),
140        }
141    }
142
143    fn insert(&mut self, element: T) -> usize {
144        match self.free_indices.pop() {
145            Some(index) => {
146                match &mut self.elements[index] {
147                    e @ StoreElement::Empty => *e = StoreElement::Occupied(element),
148                    StoreElement::Occupied(..) => panic!("bug: slot already occupied"),
149                };
150                index
151            }
152            None => {
153                let index = self.elements.len();
154                self.elements.push(StoreElement::Occupied(element));
155                index
156            }
157        }
158    }
159
160    fn set(&mut self, index: usize, element: T) {
161        match &mut self.elements[index] {
162            StoreElement::Empty => panic!("bug: set on empty element!"),
163            StoreElement::Occupied(ref mut entry) => *entry = element,
164        }
165    }
166
167    fn free(&mut self, index: usize) -> T {
168        self.free_indices.push(index);
169
170        let value = std::mem::replace(&mut self.elements[index], StoreElement::Empty);
171
172        match value {
173            StoreElement::Occupied(value) => value,
174            StoreElement::Empty => panic!("bug: freeing an empty slot"),
175        }
176    }
177}
178
179impl<T> ops::Index<usize> for Store<T> {
180    type Output = T;
181    fn index(&self, index: usize) -> &Self::Output {
182        match self.elements[index] {
183            StoreElement::Occupied(ref e) => e,
184            StoreElement::Empty => panic!("bug: indexing an empty element!"),
185        }
186    }
187}
188
189impl<T> ops::IndexMut<usize> for Store<T> {
190    fn index_mut(&mut self, index: usize) -> &mut T {
191        match self.elements[index] {
192            StoreElement::Occupied(ref mut e) => e,
193            StoreElement::Empty => panic!("bug: indexing an empty element!"),
194        }
195    }
196}
197
198#[cfg_attr(feature = "capture", derive(Serialize))]
199#[cfg_attr(feature = "replay", derive(Deserialize))]
200struct SpatialNodeEntry {
201    index: usize,
202    last_used: u64,
203}
204
205/// The representation of the spatial tree during scene building, which is
206/// mostly write-only, with a small number of queries for snapping,
207/// picture cache building
208#[cfg_attr(feature = "capture", derive(Serialize))]
209#[cfg_attr(feature = "replay", derive(Deserialize))]
210pub struct SceneSpatialTree {
211    /// Nodes which determine the positions (offsets and transforms) for primitives
212    /// and clips.
213    spatial_nodes: Store<SceneSpatialNode>,
214
215    /// A set of the uids we've encountered for spatial nodes, used to assert that
216    /// we're not seeing duplicates. Likely to be removed once we rely on this feature.
217    spatial_node_map: FastHashMap<SpatialNodeUid, SpatialNodeEntry>,
218
219    root_reference_frame_index: SpatialNodeIndex,
220
221    frame_counter: u64,
222    updates: SpatialTreeUpdates,
223
224    /// A debug check that the caller never adds a spatial node with duplicate
225    /// uid, since that can cause badness if it occurs (e.g. a malformed spatial
226    /// tree and infinite loops in is_ancestor etc)
227    spatial_nodes_set: FastHashSet<SpatialNodeUid>,
228}
229
230impl SpatialNodeContainer for SceneSpatialTree {
231    fn get_node_info(&self, index: SpatialNodeIndex) -> SpatialNodeInfo {
232        let node = &self.spatial_nodes[index.0 as usize];
233
234        SpatialNodeInfo {
235            parent: node.parent,
236            node_type: &node.descriptor.node_type,
237            snapping_transform: node.snapping_transform,
238        }
239    }
240}
241
242impl SceneSpatialTree {
243    pub fn new() -> Self {
244        let mut tree = SceneSpatialTree {
245            spatial_nodes: Store::new(),
246            spatial_node_map: FastHashMap::default(),
247            root_reference_frame_index: SpatialNodeIndex(0),
248            frame_counter: 0,
249            updates: SpatialTreeUpdates::new(),
250            spatial_nodes_set: FastHashSet::default(),
251        };
252
253        let node = SceneSpatialNode::new_reference_frame(
254            None,
255            TransformStyle::Flat,
256            PropertyBinding::Value(LayoutTransform::identity()),
257            ReferenceFrameKind::Transform {
258                should_snap: true,
259                is_2d_scale_translation: true,
260                paired_with_perspective: false,
261            },
262            LayoutVector2D::zero(),
263            PipelineId::dummy(),
264            true,
265            true,
266        );
267
268        tree.add_spatial_node(node, SpatialNodeUid::root());
269
270        tree
271    }
272
273    pub fn is_root_coord_system(&self, index: SpatialNodeIndex) -> bool {
274        self.spatial_nodes[index.0 as usize].is_root_coord_system
275    }
276
277    /// Complete building this scene, return the updates to apply to the frame spatial tree
278    pub fn end_frame_and_get_pending_updates(&mut self) -> SpatialTreeUpdates {
279        self.updates.root_reference_frame_index = self.root_reference_frame_index;
280        self.spatial_nodes_set.clear();
281
282        let now = self.frame_counter;
283        let spatial_nodes = &mut self.spatial_nodes;
284        let updates = &mut self.updates;
285
286        self.spatial_node_map.get_mut(&SpatialNodeUid::root()).unwrap().last_used = now;
287
288        self.spatial_node_map.retain(|_, entry| {
289            if entry.last_used + 10 < now {
290                spatial_nodes.free(entry.index);
291                updates.updates.push(SpatialTreeUpdate::Remove {
292                    index: entry.index,
293                });
294                return false;
295            }
296
297            true
298        });
299
300        let updates = std::mem::replace(&mut self.updates, SpatialTreeUpdates::new());
301
302        self.frame_counter += 1;
303
304        updates
305    }
306
307    /// Check if a given spatial node is an ancestor of another spatial node.
308    pub fn is_ancestor(
309        &self,
310        maybe_parent: SpatialNodeIndex,
311        maybe_child: SpatialNodeIndex,
312    ) -> bool {
313        // Early out if same node
314        if maybe_parent == maybe_child {
315            return false;
316        }
317
318        let mut current_node = maybe_child;
319
320        while current_node != self.root_reference_frame_index {
321            let node = self.get_node_info(current_node);
322            current_node = node.parent.expect("bug: no parent");
323
324            if current_node == maybe_parent {
325                return true;
326            }
327        }
328
329        false
330    }
331
332    /// Find the spatial node that is the scroll root for a given spatial node.
333    /// A scroll root is the first spatial node when found travelling up the
334    /// spatial node tree that is an explicit scroll frame.
335    pub fn find_scroll_root(
336        &self,
337        spatial_node_index: SpatialNodeIndex,
338        allow_sticky_frames: bool,
339    ) -> SpatialNodeIndex {
340        let mut real_scroll_root = self.root_reference_frame_index;
341        let mut outermost_scroll_root = self.root_reference_frame_index;
342        let mut current_scroll_root_is_sticky = false;
343        let mut node_index = spatial_node_index;
344
345        while node_index != self.root_reference_frame_index {
346            let node = self.get_node_info(node_index);
347            match node.node_type {
348                SpatialNodeType::ReferenceFrame(ref info) => {
349                    match info.kind {
350                        ReferenceFrameKind::Transform { is_2d_scale_translation: true, .. } => {
351                            // We can handle scroll nodes that pass through a 2d scale/translation node
352                        }
353                        ReferenceFrameKind::Transform { is_2d_scale_translation: false, .. } |
354                        ReferenceFrameKind::Perspective { .. } => {
355                            // When a reference frame is encountered, forget any scroll roots
356                            // we have encountered, as they may end up with a non-axis-aligned transform.
357                            real_scroll_root = self.root_reference_frame_index;
358                            outermost_scroll_root = self.root_reference_frame_index;
359                            current_scroll_root_is_sticky = false;
360                        }
361                    }
362                }
363                SpatialNodeType::StickyFrame(..) => {
364                    // Though not a scroll frame, we optionally treat sticky frames as scroll roots
365                    // to ensure they are given a separate picture cache slice.
366                    if allow_sticky_frames {
367                        outermost_scroll_root = node_index;
368                        real_scroll_root = node_index;
369                        // Set this true so that we don't select an ancestor scroll frame as the scroll root
370                        // on a subsequent iteration.
371                        current_scroll_root_is_sticky = true;
372                    }
373                }
374                SpatialNodeType::ScrollFrame(ref info) => {
375                    match info.frame_kind {
376                        ScrollFrameKind::PipelineRoot { is_root_pipeline } => {
377                            // Once we encounter a pipeline root, there is no need to look further
378                            if is_root_pipeline {
379                                break;
380                            }
381                        }
382                        ScrollFrameKind::Explicit => {
383                            // Store the closest scroll root we find to the root, for use
384                            // later on, even if it's not actually scrollable.
385                            outermost_scroll_root = node_index;
386
387                            // If the previously identified scroll root is sticky then we don't
388                            // want to choose an ancestor scroll root, as we want the sticky item
389                            // to have its own picture cache slice.
390                            if !current_scroll_root_is_sticky {
391                                // If the scroll root has no scrollable area, we don't want to
392                                // consider it. This helps pages that have a nested scroll root
393                                // within a redundant scroll root to avoid selecting the wrong
394                                // reference spatial node for a picture cache.
395                                if info.scrollable_size.width > MIN_SCROLLABLE_AMOUNT ||
396                                   info.scrollable_size.height > MIN_SCROLLABLE_AMOUNT {
397                                    // Since we are skipping redundant scroll roots, we may end up
398                                    // selecting inner scroll roots that are very small. There is
399                                    // no performance benefit to creating a slice for these roots,
400                                    // as they are cheap to rasterize. The size comparison is in
401                                    // local-space, but makes for a reasonable estimate. The value
402                                    // is arbitrary, but is generally small enough to ignore things
403                                    // like scroll roots around text input elements.
404                                    if info.viewport_rect.width() > MIN_SCROLL_ROOT_SIZE &&
405                                       info.viewport_rect.height() > MIN_SCROLL_ROOT_SIZE {
406                                        // If we've found a root that is scrollable, and a reasonable
407                                        // size, select that as the current root for this node
408                                        real_scroll_root = node_index;
409                                    }
410                                }
411                            }
412                        }
413                    }
414                }
415            }
416            node_index = node.parent.expect("unable to find parent node");
417        }
418
419        // If we didn't find any real (scrollable) frames, then return the outermost
420        // redundant scroll frame. This is important so that we can correctly find
421        // the clips defined on the content which should be handled when drawing the
422        // picture cache tiles (by definition these clips are ancestors of the
423        // scroll root selected for the picture cache).
424        if real_scroll_root == self.root_reference_frame_index {
425            outermost_scroll_root
426        } else {
427            real_scroll_root
428        }
429    }
430
431    /// The root reference frame, which is the true root of the SpatialTree.
432    pub fn root_reference_frame_index(&self) -> SpatialNodeIndex {
433        self.root_reference_frame_index
434    }
435
436    fn add_spatial_node(
437        &mut self,
438        mut node: SceneSpatialNode,
439        uid: SpatialNodeUid,
440    ) -> SpatialNodeIndex {
441        let parent_snapping_transform = match node.parent {
442            Some(parent_index) => {
443                self.get_node_info(parent_index).snapping_transform
444            }
445            None => {
446                Some(ScaleOffset::identity())
447            }
448        };
449
450        node.snapping_transform = calculate_snapping_transform(
451            parent_snapping_transform,
452            &node.descriptor.node_type,
453        );
454
455        // Ensure a node with the same uid hasn't been added during this scene build
456        assert!(self.spatial_nodes_set.insert(uid), "duplicate key {:?}", uid);
457
458        let index = match self.spatial_node_map.entry(uid) {
459            Entry::Occupied(mut e) => {
460                let e = e.get_mut();
461                e.last_used = self.frame_counter;
462
463                let existing_node = &self.spatial_nodes[e.index];
464
465                if *existing_node != node {
466                    self.updates.updates.push(SpatialTreeUpdate::Update {
467                        index: e.index,
468                        parent: node.parent,
469                        descriptor: node.descriptor.clone(),
470                    });
471                    self.spatial_nodes.set(e.index, node);
472                }
473
474                e.index
475            }
476            Entry::Vacant(e) => {
477                let descriptor = node.descriptor.clone();
478                let parent = node.parent;
479
480                let index = self.spatial_nodes.insert(node);
481
482                e.insert(SpatialNodeEntry {
483                    index,
484                    last_used: self.frame_counter,
485                });
486
487                self.updates.updates.push(SpatialTreeUpdate::Insert {
488                    index,
489                    descriptor,
490                    parent,
491                });
492
493                index
494            }
495        };
496
497        SpatialNodeIndex(index as u32)
498    }
499
500    pub fn add_reference_frame(
501        &mut self,
502        parent_index: SpatialNodeIndex,
503        transform_style: TransformStyle,
504        source_transform: PropertyBinding<LayoutTransform>,
505        kind: ReferenceFrameKind,
506        origin_in_parent_reference_frame: LayoutVector2D,
507        pipeline_id: PipelineId,
508        uid: SpatialNodeUid,
509    ) -> SpatialNodeIndex {
510        // Determine if this reference frame creates a new static coordinate system
511        let new_static_coord_system = match kind {
512            ReferenceFrameKind::Transform { is_2d_scale_translation: true, .. } => {
513                // Client has guaranteed this transform will only be axis-aligned
514                false
515            }
516            ReferenceFrameKind::Transform { is_2d_scale_translation: false, .. } | ReferenceFrameKind::Perspective { .. } => {
517                // Even if client hasn't promised it's an axis-aligned transform, we can still
518                // check this so long as the transform isn't animated (and thus could change to
519                // anything by APZ during frame building)
520                match source_transform {
521                    PropertyBinding::Value(m) => {
522                        !m.is_2d_scale_translation()
523                    }
524                    PropertyBinding::Binding(..) => {
525                        // Animated, so assume it may introduce a complex transform
526                        true
527                    }
528                }
529            }
530        };
531
532        let is_root_coord_system = !new_static_coord_system &&
533            self.spatial_nodes[parent_index.0 as usize].is_root_coord_system;
534        let is_pipeline_root = match uid.kind {
535            SpatialNodeUidKind::InternalReferenceFrame { .. } => true,
536            _ => false,
537        };
538
539        let node = SceneSpatialNode::new_reference_frame(
540            Some(parent_index),
541            transform_style,
542            source_transform,
543            kind,
544            origin_in_parent_reference_frame,
545            pipeline_id,
546            is_root_coord_system,
547            is_pipeline_root,
548        );
549        self.add_spatial_node(node, uid)
550    }
551
552    pub fn add_scroll_frame(
553        &mut self,
554        parent_index: SpatialNodeIndex,
555        external_id: ExternalScrollId,
556        pipeline_id: PipelineId,
557        frame_rect: &LayoutRect,
558        content_size: &LayoutSize,
559        frame_kind: ScrollFrameKind,
560        external_scroll_offset: LayoutVector2D,
561        scroll_offset_generation: APZScrollGeneration,
562        has_scroll_linked_effect: HasScrollLinkedEffect,
563        uid: SpatialNodeUid,
564    ) -> SpatialNodeIndex {
565        // Scroll frames are only 2d translations - they can't introduce a new static coord system
566        let is_root_coord_system = self.spatial_nodes[parent_index.0 as usize].is_root_coord_system;
567
568        let node = SceneSpatialNode::new_scroll_frame(
569            pipeline_id,
570            parent_index,
571            external_id,
572            frame_rect,
573            content_size,
574            frame_kind,
575            external_scroll_offset,
576            scroll_offset_generation,
577            has_scroll_linked_effect,
578            is_root_coord_system,
579        );
580        self.add_spatial_node(node, uid)
581    }
582
583    pub fn add_sticky_frame(
584        &mut self,
585        parent_index: SpatialNodeIndex,
586        sticky_frame_info: StickyFrameInfo,
587        pipeline_id: PipelineId,
588        key: SpatialTreeItemKey,
589        instance_id: PipelineInstanceId,
590    ) -> SpatialNodeIndex {
591        // Sticky frames are only 2d translations - they can't introduce a new static coord system
592        let is_root_coord_system = self.spatial_nodes[parent_index.0 as usize].is_root_coord_system;
593        let uid = SpatialNodeUid::external(key, pipeline_id, instance_id);
594
595        let node = SceneSpatialNode::new_sticky_frame(
596            parent_index,
597            sticky_frame_info,
598            pipeline_id,
599            is_root_coord_system,
600        );
601        self.add_spatial_node(node, uid)
602    }
603}
604
605#[cfg_attr(feature = "capture", derive(Serialize))]
606#[cfg_attr(feature = "replay", derive(Deserialize))]
607pub enum SpatialTreeUpdate {
608    Insert {
609        index: usize,
610        parent: Option<SpatialNodeIndex>,
611        descriptor: SpatialNodeDescriptor,
612    },
613    Update {
614        index: usize,
615        parent: Option<SpatialNodeIndex>,
616        descriptor: SpatialNodeDescriptor,
617    },
618    Remove {
619        index: usize,
620    },
621}
622
623/// The delta updates to apply after building a new scene to the retained frame building
624/// tree.
625// TODO(gw): During the initial scaffolding work, this is the exact same as previous
626//           behavior - that is, a complete list of new spatial nodes. In future, this
627//           will instead be a list of deltas to apply to the frame spatial tree.
628#[cfg_attr(feature = "capture", derive(Serialize))]
629#[cfg_attr(feature = "replay", derive(Deserialize))]
630pub struct SpatialTreeUpdates {
631    root_reference_frame_index: SpatialNodeIndex,
632    updates: Vec<SpatialTreeUpdate>,
633}
634
635impl SpatialTreeUpdates {
636    fn new() -> Self {
637        SpatialTreeUpdates {
638            root_reference_frame_index: SpatialNodeIndex::INVALID,
639            updates: Vec::new(),
640        }
641    }
642}
643
644/// Represents the spatial tree during frame building, which is mostly
645/// read-only, apart from the tree update at the start of the frame
646#[cfg_attr(feature = "capture", derive(Serialize))]
647#[cfg_attr(feature = "replay", derive(Deserialize))]
648pub struct SpatialTree {
649    /// Nodes which determine the positions (offsets and transforms) for primitives
650    /// and clips.
651    spatial_nodes: Vec<SpatialNode>,
652
653    /// A list of transforms that establish new coordinate systems.
654    /// Spatial nodes only establish a new coordinate system when
655    /// they have a transform that is not a simple 2d translation.
656    coord_systems: Vec<CoordinateSystem>,
657
658    root_reference_frame_index: SpatialNodeIndex,
659
660    /// Stack of current state for each parent node while traversing and updating tree
661    update_state_stack: Vec<TransformUpdateState>,
662}
663
664#[derive(Clone)]
665#[cfg_attr(feature = "capture", derive(Serialize))]
666#[cfg_attr(feature = "replay", derive(Deserialize))]
667pub struct TransformUpdateState {
668    pub parent_reference_frame_transform: LayoutToWorldFastTransform,
669    pub parent_accumulated_scroll_offset: LayoutVector2D,
670    pub nearest_scrolling_ancestor_offset: LayoutVector2D,
671    pub nearest_scrolling_ancestor_viewport: LayoutRect,
672
673    /// An id for keeping track of the axis-aligned space of this node. This is used in
674    /// order to to track what kinds of clip optimizations can be done for a particular
675    /// display list item, since optimizations can usually only be done among
676    /// coordinate systems which are relatively axis aligned.
677    pub current_coordinate_system_id: CoordinateSystemId,
678
679    /// Scale and offset from the coordinate system that started this compatible coordinate system.
680    pub coordinate_system_relative_scale_offset: ScaleOffset,
681
682    /// True if this node is transformed by an invertible transform.  If not, display items
683    /// transformed by this node will not be displayed and display items not transformed by this
684    /// node will not be clipped by clips that are transformed by this node.
685    pub invertible: bool,
686
687    /// True if this node is a part of Preserve3D hierarchy.
688    pub preserves_3d: bool,
689
690    /// True if the any parent nodes are currently zooming
691    pub is_ancestor_or_self_zooming: bool,
692
693    /// Set to true if this state represents a scroll node with external id
694    pub external_id: Option<ExternalScrollId>,
695
696    /// The node scroll offset if this state is a scroll/sticky node. Zero if a reference frame.
697    pub scroll_offset: LayoutVector2D,
698}
699
700/// Transformation between two nodes in the spatial tree that can sometimes be
701/// encoded more efficiently than with a full matrix.
702#[derive(Debug, Clone)]
703pub enum CoordinateSpaceMapping<Src, Dst> {
704    Local,
705    ScaleOffset(ScaleOffset),
706    Transform(Transform3D<f32, Src, Dst>),
707}
708
709impl<Src, Dst> CoordinateSpaceMapping<Src, Dst> {
710    pub fn into_transform(self) -> Transform3D<f32, Src, Dst> {
711        match self {
712            CoordinateSpaceMapping::Local => Transform3D::identity(),
713            CoordinateSpaceMapping::ScaleOffset(scale_offset) => scale_offset.to_transform(),
714            CoordinateSpaceMapping::Transform(transform) => transform,
715        }
716    }
717
718    pub fn into_fast_transform(self) -> FastTransform<Src, Dst> {
719        match self {
720            CoordinateSpaceMapping::Local => FastTransform::identity(),
721            CoordinateSpaceMapping::ScaleOffset(scale_offset) => FastTransform::with_scale_offset(scale_offset),
722            CoordinateSpaceMapping::Transform(transform) => FastTransform::with_transform(transform),
723        }
724    }
725
726    pub fn is_perspective(&self) -> bool {
727        match *self {
728            CoordinateSpaceMapping::Local |
729            CoordinateSpaceMapping::ScaleOffset(_) => false,
730            CoordinateSpaceMapping::Transform(ref transform) => transform.has_perspective_component(),
731        }
732    }
733
734    pub fn is_2d_axis_aligned(&self) -> bool {
735        match *self {
736            CoordinateSpaceMapping::Local |
737            CoordinateSpaceMapping::ScaleOffset(_) => true,
738            CoordinateSpaceMapping::Transform(ref transform) => transform.preserves_2d_axis_alignment(),
739        }
740    }
741
742    pub fn is_2d_scale_translation(&self) -> bool {
743        match *self {
744            CoordinateSpaceMapping::Local |
745            CoordinateSpaceMapping::ScaleOffset(_) => true,
746            CoordinateSpaceMapping::Transform(ref transform) => transform.is_2d_scale_translation(),
747        }
748    }
749
750    pub fn scale_factors(&self) -> (f32, f32) {
751        match *self {
752            CoordinateSpaceMapping::Local => (1.0, 1.0),
753            CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => (scale_offset.scale.x.abs(), scale_offset.scale.y.abs()),
754            CoordinateSpaceMapping::Transform(ref transform) => scale_factors(transform),
755        }
756    }
757
758    pub fn inverse(&self) -> Option<CoordinateSpaceMapping<Dst, Src>> {
759        match *self {
760            CoordinateSpaceMapping::Local => Some(CoordinateSpaceMapping::Local),
761            CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
762                Some(CoordinateSpaceMapping::ScaleOffset(scale_offset.inverse()))
763            }
764            CoordinateSpaceMapping::Transform(ref transform) => {
765                transform.inverse().map(CoordinateSpaceMapping::Transform)
766            }
767        }
768    }
769
770    pub fn as_2d_scale_offset(&self) -> Option<ScaleOffset> {
771        Some(match *self {
772            CoordinateSpaceMapping::Local => ScaleOffset::identity(),
773            CoordinateSpaceMapping::ScaleOffset(transfrom) => transfrom,
774            CoordinateSpaceMapping::Transform(ref transform) => {
775                if !transform.is_2d_scale_translation() {
776                    return None
777                }
778                ScaleOffset::new(transform.m11, transform.m22, transform.m41, transform.m42)
779            }
780        })
781    }
782}
783
784enum TransformScroll {
785    Scrolled,
786    Unscrolled,
787}
788
789impl SpatialNodeContainer for SpatialTree {
790    fn get_node_info(&self, index: SpatialNodeIndex) -> SpatialNodeInfo {
791        let node = self.get_spatial_node(index);
792
793        SpatialNodeInfo {
794            parent: node.parent,
795            node_type: &node.node_type,
796            snapping_transform: node.snapping_transform,
797        }
798    }
799}
800
801impl SpatialTree {
802    pub fn new() -> Self {
803        SpatialTree {
804            spatial_nodes: Vec::new(),
805            coord_systems: Vec::new(),
806            root_reference_frame_index: SpatialNodeIndex::INVALID,
807            update_state_stack: Vec::new(),
808        }
809    }
810
811    fn visit_node_impl_mut<F>(
812        &mut self,
813        index: SpatialNodeIndex,
814        f: &mut F,
815    ) where F: FnMut(SpatialNodeIndex, &mut SpatialNode) {
816        let mut child_indices: SmallVec<[SpatialNodeIndex; 8]> = SmallVec::new();
817
818        let node = self.get_spatial_node_mut(index);
819        f(index, node);
820        child_indices.extend_from_slice(&node.children);
821
822        for child_index in child_indices {
823            self.visit_node_impl_mut(child_index, f);
824        }
825    }
826
827    fn visit_node_impl<F>(
828        &self,
829        index: SpatialNodeIndex,
830        f: &mut F,
831    ) where F: FnMut(SpatialNodeIndex, &SpatialNode) {
832        let node = self.get_spatial_node(index);
833
834        f(index, node);
835
836        for child_index in &node.children {
837            self.visit_node_impl(*child_index, f);
838        }
839    }
840
841    /// Visit all nodes from the root of the tree, invoking a closure on each one
842    pub fn visit_nodes<F>(&self, mut f: F) where F: FnMut(SpatialNodeIndex, &SpatialNode) {
843        if self.root_reference_frame_index == SpatialNodeIndex::INVALID {
844            return;
845        }
846
847        self.visit_node_impl(self.root_reference_frame_index, &mut f);
848    }
849
850    /// Visit all nodes from the root of the tree, invoking a closure on each one
851    pub fn visit_nodes_mut<F>(&mut self, mut f: F) where F: FnMut(SpatialNodeIndex, &mut SpatialNode) {
852        if self.root_reference_frame_index == SpatialNodeIndex::INVALID {
853            return;
854        }
855
856        self.visit_node_impl_mut(self.root_reference_frame_index, &mut f);
857    }
858
859    /// Apply updates from a new scene to the frame spatial tree
860    pub fn apply_updates(
861        &mut self,
862        updates: SpatialTreeUpdates,
863    ) {
864        self.root_reference_frame_index = updates.root_reference_frame_index;
865
866        for update in updates.updates {
867            match update {
868                SpatialTreeUpdate::Insert { index, parent, descriptor } => {
869                    if let Some(parent) = parent {
870                        self.get_spatial_node_mut(parent).add_child(SpatialNodeIndex(index as u32));
871                    }
872
873                    let node = SpatialNode {
874                        viewport_transform: ScaleOffset::identity(),
875                        content_transform: ScaleOffset::identity(),
876                        snapping_transform: None,
877                        coordinate_system_id: CoordinateSystemId(0),
878                        transform_kind: TransformedRectKind::AxisAligned,
879                        parent,
880                        children: Vec::new(),
881                        pipeline_id: descriptor.pipeline_id,
882                        node_type: descriptor.node_type,
883                        invertible: true,
884                        is_async_zooming: false,
885                        is_ancestor_or_self_zooming: false,
886                    };
887
888                    assert!(index <= self.spatial_nodes.len());
889                    if index < self.spatial_nodes.len() {
890                        self.spatial_nodes[index] = node;
891                    } else {
892                        self.spatial_nodes.push(node);
893                    }
894                }
895                SpatialTreeUpdate::Update { index, descriptor, parent } => {
896                    let current_parent = self.spatial_nodes[index].parent;
897
898                    if current_parent != parent {
899                        if let Some(current_parent) = current_parent {
900                            let i = self.spatial_nodes[current_parent.0 as usize]
901                                .children
902                                .iter()
903                                .position(|e| e.0 as usize == index)
904                                .expect("bug: not found!");
905                            self.spatial_nodes[current_parent.0 as usize].children.remove(i);
906                        }
907
908                        let new_parent = parent.expect("todo: is this valid?");
909                        self.spatial_nodes[new_parent.0 as usize].add_child(SpatialNodeIndex(index as u32));
910                    }
911
912                    let node = &mut self.spatial_nodes[index];
913
914                    node.node_type = descriptor.node_type;
915                    node.pipeline_id = descriptor.pipeline_id;
916                    node.parent = parent;
917                }
918                SpatialTreeUpdate::Remove { index, .. } => {
919                    let node = &mut self.spatial_nodes[index];
920
921                    // Set the pipeline id to be invalid, so that even though this array
922                    // entry still exists we can easily see it's invalid when debugging.
923                    node.pipeline_id = PipelineId::dummy();
924
925                    if let Some(parent) = node.parent {
926                        let i = self.spatial_nodes[parent.0 as usize]
927                            .children
928                            .iter()
929                            .position(|e| e.0 as usize == index)
930                            .expect("bug: not found!");
931                        self.spatial_nodes[parent.0 as usize].children.remove(i);
932                    }
933                }
934            }
935        }
936
937        self.visit_nodes_mut(|_, node| {
938            match node.node_type {
939                SpatialNodeType::ScrollFrame(ref mut info) => {
940                    info.offsets = vec![SampledScrollOffset{
941                        offset: -info.external_scroll_offset,
942                        generation: info.offset_generation,
943                    }];
944                }
945                SpatialNodeType::StickyFrame(ref mut info) => {
946                    info.current_offset = LayoutVector2D::zero();
947                }
948                SpatialNodeType::ReferenceFrame(..) => {}
949            }
950        });
951    }
952
953    pub fn get_last_sampled_scroll_offsets(
954        &self,
955    ) -> FastHashMap<ExternalScrollId, Vec<SampledScrollOffset>> {
956        let mut result = FastHashMap::default();
957        self.visit_nodes(|_, node| {
958            if let SpatialNodeType::ScrollFrame(ref scrolling) = node.node_type {
959                result.insert(scrolling.external_id, scrolling.offsets.clone());
960            }
961        });
962        result
963    }
964
965    pub fn apply_last_sampled_scroll_offsets(
966        &mut self,
967        last_sampled_offsets: FastHashMap<ExternalScrollId, Vec<SampledScrollOffset>>,
968    ) {
969        self.visit_nodes_mut(|_, node| {
970            if let SpatialNodeType::ScrollFrame(ref mut scrolling) = node.node_type {
971                if let Some(offsets) = last_sampled_offsets.get(&scrolling.external_id) {
972                    scrolling.offsets = offsets.clone();
973                }
974            }
975        });
976    }
977
978    pub fn get_spatial_node(&self, index: SpatialNodeIndex) -> &SpatialNode {
979        &self.spatial_nodes[index.0 as usize]
980    }
981
982    pub fn get_spatial_node_mut(&mut self, index: SpatialNodeIndex) -> &mut SpatialNode {
983        &mut self.spatial_nodes[index.0 as usize]
984    }
985
986    /// Get total number of spatial nodes
987    pub fn spatial_node_count(&self) -> usize {
988        self.spatial_nodes.len()
989    }
990
991    pub fn find_spatial_node_by_anim_id(
992        &self,
993        id: PropertyBindingId,
994    ) -> Option<SpatialNodeIndex> {
995        let mut node_index = None;
996
997        self.visit_nodes(|index, node| {
998            if node.is_transform_bound_to_property(id) {
999                debug_assert!(node_index.is_none());        // Multiple nodes with same anim id
1000                node_index = Some(index);
1001            }
1002        });
1003
1004        node_index
1005    }
1006
1007    /// Calculate the relative transform from `child_index` to `parent_index`.
1008    /// This method will panic if the nodes are not connected!
1009    pub fn get_relative_transform(
1010        &self,
1011        child_index: SpatialNodeIndex,
1012        parent_index: SpatialNodeIndex,
1013    ) -> CoordinateSpaceMapping<LayoutPixel, LayoutPixel> {
1014        self.get_relative_transform_with_face(child_index, parent_index, None)
1015    }
1016
1017    /// Calculate the relative transform from `child_index` to `parent_index`.
1018    /// This method will panic if the nodes are not connected!
1019    /// Also, switch the visible face to `Back` if at any stage where the
1020    /// combined transform is flattened, we see the back face.
1021    pub fn get_relative_transform_with_face(
1022        &self,
1023        child_index: SpatialNodeIndex,
1024        parent_index: SpatialNodeIndex,
1025        mut visible_face: Option<&mut VisibleFace>,
1026    ) -> CoordinateSpaceMapping<LayoutPixel, LayoutPixel> {
1027        if child_index == parent_index {
1028            return CoordinateSpaceMapping::Local;
1029        }
1030
1031        let child = self.get_spatial_node(child_index);
1032        let parent = self.get_spatial_node(parent_index);
1033
1034        // TODO(gw): We expect this never to fail, but it's possible that it might due to
1035        //           either (a) a bug in WR / Gecko, or (b) some obscure real-world content
1036        //           that we're unaware of. If we ever hit this, please open a bug with any
1037        //           repro steps!
1038        assert!(
1039            child.coordinate_system_id.0 >= parent.coordinate_system_id.0,
1040            "bug: this is an unexpected case - please open a bug and talk to #gfx team!",
1041        );
1042
1043        if child.coordinate_system_id == parent.coordinate_system_id {
1044            let scale_offset = child.content_transform.then(&parent.content_transform.inverse());
1045            return CoordinateSpaceMapping::ScaleOffset(scale_offset);
1046        }
1047
1048        let mut coordinate_system_id = child.coordinate_system_id;
1049        let mut transform = child.content_transform.to_transform();
1050
1051        // we need to update the associated parameters of a transform in two cases:
1052        // 1) when the flattening happens, so that we don't lose that original 3D aspects
1053        // 2) when we reach the end of iteration, so that our result is up to date
1054
1055        while coordinate_system_id != parent.coordinate_system_id {
1056            let coord_system = &self.coord_systems[coordinate_system_id.0 as usize];
1057
1058            if coord_system.should_flatten {
1059                if let Some(ref mut face) = visible_face {
1060                    if transform.is_backface_visible() {
1061                        **face = VisibleFace::Back;
1062                    }
1063                }
1064                transform.flatten_z_output();
1065            }
1066
1067            coordinate_system_id = coord_system.parent.expect("invalid parent!");
1068            transform = transform.then(&coord_system.transform);
1069        }
1070
1071        transform = transform.then(
1072            &parent.content_transform
1073                .inverse()
1074                .to_transform(),
1075        );
1076        if let Some(face) = visible_face {
1077            if transform.is_backface_visible() {
1078                *face = VisibleFace::Back;
1079            }
1080        }
1081
1082        CoordinateSpaceMapping::Transform(transform)
1083    }
1084
1085    /// Returns true if both supplied spatial nodes are in the same coordinate system
1086    /// (implies the relative transform produce axis-aligned rects).
1087    pub fn is_matching_coord_system(
1088        &self,
1089        index0: SpatialNodeIndex,
1090        index1: SpatialNodeIndex,
1091    ) -> bool {
1092        let node0 = self.get_spatial_node(index0);
1093        let node1 = self.get_spatial_node(index1);
1094
1095        node0.coordinate_system_id == node1.coordinate_system_id
1096    }
1097
1098    fn get_world_transform_impl(
1099        &self,
1100        index: SpatialNodeIndex,
1101        scroll: TransformScroll,
1102    ) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> {
1103        let child = self.get_spatial_node(index);
1104
1105        if child.coordinate_system_id.0 == 0 {
1106            if index == self.root_reference_frame_index {
1107                CoordinateSpaceMapping::Local
1108            } else {
1109              match scroll {
1110                TransformScroll::Scrolled => CoordinateSpaceMapping::ScaleOffset(child.content_transform),
1111                TransformScroll::Unscrolled => CoordinateSpaceMapping::ScaleOffset(child.viewport_transform),
1112              }
1113            }
1114        } else {
1115            let system = &self.coord_systems[child.coordinate_system_id.0 as usize];
1116            let scale_offset = match scroll {
1117                TransformScroll::Scrolled => &child.content_transform,
1118                TransformScroll::Unscrolled => &child.viewport_transform,
1119            };
1120            let transform = scale_offset
1121                .to_transform()
1122                .then(&system.world_transform);
1123
1124            CoordinateSpaceMapping::Transform(transform)
1125        }
1126    }
1127
1128    /// Calculate the relative transform from `index` to the root.
1129    pub fn get_world_transform(
1130        &self,
1131        index: SpatialNodeIndex,
1132    ) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> {
1133        self.get_world_transform_impl(index, TransformScroll::Scrolled)
1134    }
1135
1136    /// Calculate the relative transform from `index` to the root.
1137    /// Unlike `get_world_transform`, this variant doesn't account for the local scroll offset.
1138    pub fn get_world_viewport_transform(
1139        &self,
1140        index: SpatialNodeIndex,
1141    ) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> {
1142        self.get_world_transform_impl(index, TransformScroll::Unscrolled)
1143    }
1144
1145    /// The root reference frame, which is the true root of the SpatialTree.
1146    pub fn root_reference_frame_index(&self) -> SpatialNodeIndex {
1147        self.root_reference_frame_index
1148    }
1149
1150    pub fn set_scroll_offsets(
1151        &mut self,
1152        id: ExternalScrollId,
1153        offsets: Vec<SampledScrollOffset>,
1154    ) -> bool {
1155        let mut did_change = false;
1156
1157        self.visit_nodes_mut(|_, node| {
1158            if node.matches_external_id(id) {
1159                did_change |= node.set_scroll_offsets(offsets.clone());
1160            }
1161        });
1162
1163        did_change
1164    }
1165
1166    pub fn update_tree(
1167        &mut self,
1168        scene_properties: &SceneProperties,
1169    ) {
1170        if self.root_reference_frame_index == SpatialNodeIndex::INVALID {
1171            return;
1172        }
1173
1174        profile_scope!("update_tree");
1175        self.coord_systems.clear();
1176        self.coord_systems.push(CoordinateSystem::root());
1177
1178        let root_node_index = self.root_reference_frame_index();
1179        assert!(self.update_state_stack.is_empty());
1180
1181        let state = TransformUpdateState {
1182            parent_reference_frame_transform: LayoutVector2D::zero().into(),
1183            parent_accumulated_scroll_offset: LayoutVector2D::zero(),
1184            nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
1185            nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
1186            current_coordinate_system_id: CoordinateSystemId::root(),
1187            coordinate_system_relative_scale_offset: ScaleOffset::identity(),
1188            invertible: true,
1189            preserves_3d: false,
1190            is_ancestor_or_self_zooming: false,
1191            external_id: None,
1192            scroll_offset: LayoutVector2D::zero(),
1193        };
1194        self.update_state_stack.push(state);
1195
1196        self.update_node(
1197            root_node_index,
1198            scene_properties,
1199        );
1200
1201        self.update_state_stack.pop().unwrap();
1202    }
1203
1204    fn update_node(
1205        &mut self,
1206        node_index: SpatialNodeIndex,
1207        scene_properties: &SceneProperties,
1208    ) {
1209        let parent_snapping_transform = match self.get_spatial_node(node_index).parent {
1210            Some(parent_index) => {
1211                self.get_node_info(parent_index).snapping_transform
1212            }
1213            None => {
1214                Some(ScaleOffset::identity())
1215            }
1216        };
1217
1218        let node = &mut self.spatial_nodes[node_index.0 as usize];
1219
1220        node.snapping_transform = calculate_snapping_transform(
1221            parent_snapping_transform,
1222            &node.node_type,
1223        );
1224
1225        node.update(
1226            &self.update_state_stack,
1227            &mut self.coord_systems,
1228            scene_properties,
1229        );
1230
1231        if !node.children.is_empty() {
1232            let mut child_state = self.update_state_stack.last().unwrap().clone();
1233            node.prepare_state_for_children(&mut child_state);
1234            self.update_state_stack.push(child_state);
1235
1236            let mut child_indices: SmallVec<[SpatialNodeIndex; 8]> = SmallVec::new();
1237            child_indices.extend_from_slice(&node.children);
1238
1239            for child_index in child_indices {
1240                self.update_node(
1241                    child_index,
1242                    scene_properties,
1243                );
1244            }
1245
1246            self.update_state_stack.pop().unwrap();
1247        }
1248    }
1249
1250    pub fn build_transform_palette(&self, memory: &FrameMemory) -> TransformPalette {
1251        profile_scope!("build_transform_palette");
1252        TransformPalette::new(self.spatial_nodes.len(), memory)
1253    }
1254
1255    fn print_node<T: PrintTreePrinter>(
1256        &self,
1257        index: SpatialNodeIndex,
1258        pt: &mut T,
1259    ) {
1260        let node = self.get_spatial_node(index);
1261        match node.node_type {
1262            SpatialNodeType::StickyFrame(ref sticky_frame_info) => {
1263                pt.new_level(format!("StickyFrame"));
1264                pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
1265            }
1266            SpatialNodeType::ScrollFrame(ref scrolling_info) => {
1267                pt.new_level(format!("ScrollFrame"));
1268                pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect));
1269                pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
1270                pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset()));
1271                pt.add_item(format!("external_scroll_offset: {:?}", scrolling_info.external_scroll_offset));
1272                pt.add_item(format!("offset generation: {:?}", scrolling_info.offset_generation));
1273                if scrolling_info.has_scroll_linked_effect == HasScrollLinkedEffect::Yes {
1274                    pt.add_item("has scroll-linked effect".to_string());
1275                }
1276                pt.add_item(format!("kind: {:?}", scrolling_info.frame_kind));
1277            }
1278            SpatialNodeType::ReferenceFrame(ref info) => {
1279                pt.new_level(format!("ReferenceFrame"));
1280                pt.add_item(format!("kind: {:?}", info.kind));
1281                pt.add_item(format!("transform_style: {:?}", info.transform_style));
1282                pt.add_item(format!("source_transform: {:?}", info.source_transform));
1283                pt.add_item(format!("origin_in_parent_reference_frame: {:?}", info.origin_in_parent_reference_frame));
1284            }
1285        }
1286
1287        pt.add_item(format!("index: {:?}", index));
1288        pt.add_item(format!("content_transform: {:?}", node.content_transform));
1289        pt.add_item(format!("viewport_transform: {:?}", node.viewport_transform));
1290        pt.add_item(format!("snapping_transform: {:?}", node.snapping_transform));
1291        pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
1292
1293        for child_index in &node.children {
1294            self.print_node(*child_index, pt);
1295        }
1296
1297        pt.end_level();
1298    }
1299
1300    /// Get the visible face of the transfrom from the specified node to its parent.
1301    pub fn get_local_visible_face(&self, node_index: SpatialNodeIndex) -> VisibleFace {
1302        let node = self.get_spatial_node(node_index);
1303        let mut face = VisibleFace::Front;
1304        if let Some(mut parent_index) = node.parent {
1305            // Check if the parent is perspective. In CSS, a stacking context may
1306            // have both perspective and a regular transformation. Gecko translates the
1307            // perspective into a different `nsDisplayPerspective` and `nsDisplayTransform` items.
1308            // On WebRender side, we end up with 2 different reference frames:
1309            // one has kind of "transform", and it's parented to another of "perspective":
1310            // https://searchfox.org/mozilla-central/rev/72c7cef167829b6f1e24cae216fa261934c455fc/layout/generic/nsIFrame.cpp#3716
1311            if let SpatialNodeType::ReferenceFrame(ReferenceFrameInfo { kind: ReferenceFrameKind::Transform {
1312                paired_with_perspective: true,
1313                ..
1314            }, .. }) = node.node_type {
1315                let parent = self.get_spatial_node(parent_index);
1316                match parent.node_type {
1317                    SpatialNodeType::ReferenceFrame(ReferenceFrameInfo {
1318                        kind: ReferenceFrameKind::Perspective { .. },
1319                        ..
1320                    }) => {
1321                        parent_index = parent.parent.unwrap();
1322                    }
1323                    _ => {
1324                        log::error!("Unexpected parent {:?} is not perspective", parent_index);
1325                    }
1326                }
1327            }
1328
1329            self.get_relative_transform_with_face(node_index, parent_index, Some(&mut face));
1330        }
1331        face
1332    }
1333
1334    #[allow(dead_code)]
1335    pub fn print_to_string(&self) -> String {
1336        let mut result = String::new();
1337
1338        if self.root_reference_frame_index != SpatialNodeIndex::INVALID {
1339            let mut buf = Vec::<u8>::new();
1340            {
1341                let mut pt = PrintTree::new_with_sink("spatial tree", &mut buf);
1342                self.print_with(&mut pt);
1343            }
1344            result = std::str::from_utf8(&buf).unwrap_or("(Tree printer emitted non-utf8)").to_string();
1345        }
1346
1347        result
1348    }
1349
1350    #[allow(dead_code)]
1351    pub fn print(&self) {
1352        let result = self.print_to_string();
1353        // If running in Gecko, set RUST_LOG=webrender::spatial_tree=debug
1354        // to get this logging to be emitted to stderr/logcat.
1355        debug!("{}", result);
1356    }
1357}
1358
1359impl PrintableTree for SpatialTree {
1360    fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
1361        if self.root_reference_frame_index != SpatialNodeIndex::INVALID {
1362            self.print_node(self.root_reference_frame_index(), pt);
1363        }
1364    }
1365}
1366
1367/// Calculate the accumulated external scroll offset for a given spatial node.
1368pub fn get_external_scroll_offset<S: SpatialNodeContainer>(
1369    spatial_tree: &S,
1370    node_index: SpatialNodeIndex,
1371) -> LayoutVector2D {
1372    let mut offset = LayoutVector2D::zero();
1373    let mut current_node = Some(node_index);
1374
1375    while let Some(node_index) = current_node {
1376        let node_info = spatial_tree.get_node_info(node_index);
1377
1378        match node_info.node_type {
1379            SpatialNodeType::ScrollFrame(ref scrolling) => {
1380                offset += scrolling.external_scroll_offset;
1381            }
1382            SpatialNodeType::StickyFrame(..) => {
1383                // Doesn't provide any external scroll offset
1384            }
1385            SpatialNodeType::ReferenceFrame(..) => {
1386                // External scroll offsets are not propagated across
1387                // reference frames.
1388                break;
1389            }
1390        }
1391
1392        current_node = node_info.parent;
1393    }
1394
1395    offset
1396}
1397
1398fn calculate_snapping_transform(
1399    parent_snapping_transform: Option<ScaleOffset>,
1400    node_type: &SpatialNodeType,
1401) -> Option<ScaleOffset> {
1402    // We need to incorporate the parent scale/offset with the child.
1403    // If the parent does not have a scale/offset, then we know we are
1404    // not 2d axis aligned and thus do not need to snap its children
1405    // either.
1406    let parent_scale_offset = match parent_snapping_transform {
1407        Some(parent_snapping_transform) => parent_snapping_transform,
1408        None => return None,
1409    };
1410
1411    let scale_offset = match node_type {
1412        SpatialNodeType::ReferenceFrame(ref info) => {
1413            match info.source_transform {
1414                PropertyBinding::Value(ref value) => {
1415                    // We can only get a ScaleOffset if the transform is 2d axis
1416                    // aligned.
1417                    match ScaleOffset::from_transform(value) {
1418                        Some(scale_offset) => {
1419                            let origin_offset = info.origin_in_parent_reference_frame;
1420                            scale_offset.then(&ScaleOffset::from_offset(origin_offset.to_untyped()))
1421                        }
1422                        None => return None,
1423                    }
1424                }
1425
1426                // Assume animations start at the identity transform for snapping purposes.
1427                // We still want to incorporate the reference frame offset however.
1428                // TODO(aosmond): Is there a better known starting point?
1429                PropertyBinding::Binding(..) => {
1430                    let origin_offset = info.origin_in_parent_reference_frame;
1431                    ScaleOffset::from_offset(origin_offset.to_untyped())
1432                }
1433            }
1434        }
1435        _ => ScaleOffset::identity(),
1436    };
1437
1438    Some(scale_offset.then(&parent_scale_offset))
1439}
1440
1441#[cfg(test)]
1442fn add_reference_frame(
1443    cst: &mut SceneSpatialTree,
1444    parent: SpatialNodeIndex,
1445    transform: LayoutTransform,
1446    origin_in_parent_reference_frame: LayoutVector2D,
1447    key: SpatialTreeItemKey,
1448) -> SpatialNodeIndex {
1449    let pid = PipelineInstanceId::new(0);
1450
1451    cst.add_reference_frame(
1452        parent,
1453        TransformStyle::Preserve3D,
1454        PropertyBinding::Value(transform),
1455        ReferenceFrameKind::Transform {
1456            is_2d_scale_translation: false,
1457            should_snap: false,
1458            paired_with_perspective: false,
1459        },
1460        origin_in_parent_reference_frame,
1461        PipelineId::dummy(),
1462        SpatialNodeUid::external(key, PipelineId::dummy(), pid),
1463    )
1464}
1465
1466#[cfg(test)]
1467fn test_pt(
1468    px: f32,
1469    py: f32,
1470    cst: &SpatialTree,
1471    child: SpatialNodeIndex,
1472    parent: SpatialNodeIndex,
1473    expected_x: f32,
1474    expected_y: f32,
1475) {
1476    use euclid::approxeq::ApproxEq;
1477    const EPSILON: f32 = 0.0001;
1478
1479    let p = LayoutPoint::new(px, py);
1480    let m = cst.get_relative_transform(child, parent).into_transform();
1481    let pt = m.transform_point2d(p).unwrap();
1482    assert!(pt.x.approx_eq_eps(&expected_x, &EPSILON) &&
1483            pt.y.approx_eq_eps(&expected_y, &EPSILON),
1484            "p: {:?} -> {:?}\nm={:?}",
1485            p, pt, m,
1486            );
1487}
1488
1489#[test]
1490fn test_cst_simple_translation() {
1491    // Basic translations only
1492
1493    let mut cst = SceneSpatialTree::new();
1494    let root_reference_frame_index = cst.root_reference_frame_index();
1495
1496    let root = add_reference_frame(
1497        &mut cst,
1498        root_reference_frame_index,
1499        LayoutTransform::identity(),
1500        LayoutVector2D::zero(),
1501        SpatialTreeItemKey::new(0, 0),
1502    );
1503
1504    let child1 = add_reference_frame(
1505        &mut cst,
1506        root,
1507        LayoutTransform::translation(100.0, 0.0, 0.0),
1508        LayoutVector2D::zero(),
1509        SpatialTreeItemKey::new(0, 1),
1510    );
1511
1512    let child2 = add_reference_frame(
1513        &mut cst,
1514        child1,
1515        LayoutTransform::translation(0.0, 50.0, 0.0),
1516        LayoutVector2D::zero(),
1517        SpatialTreeItemKey::new(0, 2),
1518    );
1519
1520    let child3 = add_reference_frame(
1521        &mut cst,
1522        child2,
1523        LayoutTransform::translation(200.0, 200.0, 0.0),
1524        LayoutVector2D::zero(),
1525        SpatialTreeItemKey::new(0, 3),
1526    );
1527
1528    let mut st = SpatialTree::new();
1529    st.apply_updates(cst.end_frame_and_get_pending_updates());
1530    st.update_tree(&SceneProperties::new());
1531
1532    test_pt(100.0, 100.0, &st, child1, root, 200.0, 100.0);
1533    test_pt(100.0, 100.0, &st, child2, root, 200.0, 150.0);
1534    test_pt(100.0, 100.0, &st, child2, child1, 100.0, 150.0);
1535    test_pt(100.0, 100.0, &st, child3, root, 400.0, 350.0);
1536}
1537
1538#[test]
1539fn test_cst_simple_scale() {
1540    // Basic scale only
1541
1542    let mut cst = SceneSpatialTree::new();
1543    let root_reference_frame_index = cst.root_reference_frame_index();
1544
1545    let root = add_reference_frame(
1546        &mut cst,
1547        root_reference_frame_index,
1548        LayoutTransform::identity(),
1549        LayoutVector2D::zero(),
1550        SpatialTreeItemKey::new(0, 0),
1551    );
1552
1553    let child1 = add_reference_frame(
1554        &mut cst,
1555        root,
1556        LayoutTransform::scale(4.0, 1.0, 1.0),
1557        LayoutVector2D::zero(),
1558        SpatialTreeItemKey::new(0, 1),
1559    );
1560
1561    let child2 = add_reference_frame(
1562        &mut cst,
1563        child1,
1564        LayoutTransform::scale(1.0, 2.0, 1.0),
1565        LayoutVector2D::zero(),
1566        SpatialTreeItemKey::new(0, 2),
1567    );
1568
1569    let child3 = add_reference_frame(
1570        &mut cst,
1571        child2,
1572        LayoutTransform::scale(2.0, 2.0, 1.0),
1573        LayoutVector2D::zero(),
1574        SpatialTreeItemKey::new(0, 3),
1575    );
1576
1577    let mut st = SpatialTree::new();
1578    st.apply_updates(cst.end_frame_and_get_pending_updates());
1579    st.update_tree(&SceneProperties::new());
1580
1581    test_pt(100.0, 100.0, &st, child1, root, 400.0, 100.0);
1582    test_pt(100.0, 100.0, &st, child2, root, 400.0, 200.0);
1583    test_pt(100.0, 100.0, &st, child3, root, 800.0, 400.0);
1584    test_pt(100.0, 100.0, &st, child2, child1, 100.0, 200.0);
1585    test_pt(100.0, 100.0, &st, child3, child1, 200.0, 400.0);
1586}
1587
1588#[test]
1589fn test_cst_scale_translation() {
1590    // Scale + translation
1591
1592    let mut cst = SceneSpatialTree::new();
1593    let root_reference_frame_index = cst.root_reference_frame_index();
1594
1595    let root = add_reference_frame(
1596        &mut cst,
1597        root_reference_frame_index,
1598        LayoutTransform::identity(),
1599        LayoutVector2D::zero(),
1600        SpatialTreeItemKey::new(0, 0),
1601    );
1602
1603    let child1 = add_reference_frame(
1604        &mut cst,
1605        root,
1606        LayoutTransform::translation(100.0, 50.0, 0.0),
1607        LayoutVector2D::zero(),
1608        SpatialTreeItemKey::new(0, 1),
1609    );
1610
1611    let child2 = add_reference_frame(
1612        &mut cst,
1613        child1,
1614        LayoutTransform::scale(2.0, 4.0, 1.0),
1615        LayoutVector2D::zero(),
1616        SpatialTreeItemKey::new(0, 2),
1617    );
1618
1619    let child3 = add_reference_frame(
1620        &mut cst,
1621        child2,
1622        LayoutTransform::translation(200.0, -100.0, 0.0),
1623        LayoutVector2D::zero(),
1624        SpatialTreeItemKey::new(0, 3),
1625    );
1626
1627    let child4 = add_reference_frame(
1628        &mut cst,
1629        child3,
1630        LayoutTransform::scale(3.0, 2.0, 1.0),
1631        LayoutVector2D::zero(),
1632        SpatialTreeItemKey::new(0, 4),
1633    );
1634
1635    let mut st = SpatialTree::new();
1636    st.apply_updates(cst.end_frame_and_get_pending_updates());
1637    st.update_tree(&SceneProperties::new());
1638
1639    test_pt(100.0, 100.0, &st, child1, root, 200.0, 150.0);
1640    test_pt(100.0, 100.0, &st, child2, root, 300.0, 450.0);
1641    test_pt(100.0, 100.0, &st, child4, root, 1100.0, 450.0);
1642
1643    test_pt(0.0, 0.0, &st, child4, child1, 400.0, -400.0);
1644    test_pt(100.0, 100.0, &st, child4, child1, 1000.0, 400.0);
1645    test_pt(100.0, 100.0, &st, child2, child1, 200.0, 400.0);
1646
1647    test_pt(100.0, 100.0, &st, child3, child1, 600.0, 0.0);
1648}
1649
1650#[test]
1651fn test_cst_translation_rotate() {
1652    // Rotation + translation
1653    use euclid::Angle;
1654
1655    let mut cst = SceneSpatialTree::new();
1656    let root_reference_frame_index = cst.root_reference_frame_index();
1657
1658    let root = add_reference_frame(
1659        &mut cst,
1660        root_reference_frame_index,
1661        LayoutTransform::identity(),
1662        LayoutVector2D::zero(),
1663        SpatialTreeItemKey::new(0, 0),
1664    );
1665
1666    let child1 = add_reference_frame(
1667        &mut cst,
1668        root,
1669        LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::degrees(-90.0)),
1670        LayoutVector2D::zero(),
1671        SpatialTreeItemKey::new(0, 1),
1672    );
1673
1674    let mut st = SpatialTree::new();
1675    st.apply_updates(cst.end_frame_and_get_pending_updates());
1676    st.update_tree(&SceneProperties::new());
1677
1678    test_pt(100.0, 0.0, &st, child1, root, 0.0, -100.0);
1679}
1680
1681#[test]
1682fn test_is_ancestor1() {
1683    let mut st = SceneSpatialTree::new();
1684    let root_reference_frame_index = st.root_reference_frame_index();
1685
1686    let root = add_reference_frame(
1687        &mut st,
1688        root_reference_frame_index,
1689        LayoutTransform::identity(),
1690        LayoutVector2D::zero(),
1691        SpatialTreeItemKey::new(0, 0),
1692    );
1693
1694    let child1_0 = add_reference_frame(
1695        &mut st,
1696        root,
1697        LayoutTransform::identity(),
1698        LayoutVector2D::zero(),
1699        SpatialTreeItemKey::new(0, 1),
1700    );
1701
1702    let child1_1 = add_reference_frame(
1703        &mut st,
1704        child1_0,
1705        LayoutTransform::identity(),
1706        LayoutVector2D::zero(),
1707        SpatialTreeItemKey::new(0, 2),
1708    );
1709
1710    let child2 = add_reference_frame(
1711        &mut st,
1712        root,
1713        LayoutTransform::identity(),
1714        LayoutVector2D::zero(),
1715        SpatialTreeItemKey::new(0, 3),
1716    );
1717
1718    assert!(!st.is_ancestor(root, root));
1719    assert!(!st.is_ancestor(child1_0, child1_0));
1720    assert!(!st.is_ancestor(child1_1, child1_1));
1721    assert!(!st.is_ancestor(child2, child2));
1722
1723    assert!(st.is_ancestor(root, child1_0));
1724    assert!(st.is_ancestor(root, child1_1));
1725    assert!(st.is_ancestor(child1_0, child1_1));
1726
1727    assert!(!st.is_ancestor(child1_0, root));
1728    assert!(!st.is_ancestor(child1_1, root));
1729    assert!(!st.is_ancestor(child1_1, child1_0));
1730
1731    assert!(st.is_ancestor(root, child2));
1732    assert!(!st.is_ancestor(child2, root));
1733
1734    assert!(!st.is_ancestor(child1_0, child2));
1735    assert!(!st.is_ancestor(child1_1, child2));
1736    assert!(!st.is_ancestor(child2, child1_0));
1737    assert!(!st.is_ancestor(child2, child1_1));
1738}
1739
1740/// Tests that we select the correct scroll root in the simple case.
1741#[test]
1742fn test_find_scroll_root_simple() {
1743    let mut st = SceneSpatialTree::new();
1744    let pid = PipelineInstanceId::new(0);
1745
1746    let root = st.add_reference_frame(
1747        st.root_reference_frame_index(),
1748        TransformStyle::Flat,
1749        PropertyBinding::Value(LayoutTransform::identity()),
1750        ReferenceFrameKind::Transform {
1751            is_2d_scale_translation: true,
1752            should_snap: true,
1753            paired_with_perspective: false,
1754        },
1755        LayoutVector2D::new(0.0, 0.0),
1756        PipelineId::dummy(),
1757        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid),
1758    );
1759
1760    let scroll = st.add_scroll_frame(
1761        root,
1762        ExternalScrollId(1, PipelineId::dummy()),
1763        PipelineId::dummy(),
1764        &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1765        &LayoutSize::new(800.0, 400.0),
1766        ScrollFrameKind::Explicit,
1767        LayoutVector2D::new(0.0, 0.0),
1768        APZScrollGeneration::default(),
1769        HasScrollLinkedEffect::No,
1770        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid),
1771    );
1772
1773    assert_eq!(st.find_scroll_root(scroll, true), scroll);
1774}
1775
1776/// Tests that we select the root scroll frame rather than the subframe if both are scrollable.
1777#[test]
1778fn test_find_scroll_root_sub_scroll_frame() {
1779    let mut st = SceneSpatialTree::new();
1780    let pid = PipelineInstanceId::new(0);
1781
1782    let root = st.add_reference_frame(
1783        st.root_reference_frame_index(),
1784        TransformStyle::Flat,
1785        PropertyBinding::Value(LayoutTransform::identity()),
1786        ReferenceFrameKind::Transform {
1787            is_2d_scale_translation: true,
1788            should_snap: true,
1789            paired_with_perspective: false,
1790        },
1791        LayoutVector2D::new(0.0, 0.0),
1792        PipelineId::dummy(),
1793        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid),
1794    );
1795
1796    let root_scroll = st.add_scroll_frame(
1797        root,
1798        ExternalScrollId(1, PipelineId::dummy()),
1799        PipelineId::dummy(),
1800        &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1801        &LayoutSize::new(800.0, 400.0),
1802        ScrollFrameKind::Explicit,
1803        LayoutVector2D::new(0.0, 0.0),
1804        APZScrollGeneration::default(),
1805        HasScrollLinkedEffect::No,
1806        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid),
1807    );
1808
1809    let sub_scroll = st.add_scroll_frame(
1810        root_scroll,
1811        ExternalScrollId(1, PipelineId::dummy()),
1812        PipelineId::dummy(),
1813        &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1814        &LayoutSize::new(800.0, 400.0),
1815        ScrollFrameKind::Explicit,
1816        LayoutVector2D::new(0.0, 0.0),
1817        APZScrollGeneration::default(),
1818        HasScrollLinkedEffect::No,
1819        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid),
1820    );
1821
1822    assert_eq!(st.find_scroll_root(sub_scroll, true), root_scroll);
1823}
1824
1825/// Tests that we select the sub scroll frame when the root scroll frame is not scrollable.
1826#[test]
1827fn test_find_scroll_root_not_scrollable() {
1828    let mut st = SceneSpatialTree::new();
1829    let pid = PipelineInstanceId::new(0);
1830
1831    let root = st.add_reference_frame(
1832        st.root_reference_frame_index(),
1833        TransformStyle::Flat,
1834        PropertyBinding::Value(LayoutTransform::identity()),
1835        ReferenceFrameKind::Transform {
1836            is_2d_scale_translation: true,
1837            should_snap: true,
1838            paired_with_perspective: false,
1839        },
1840        LayoutVector2D::new(0.0, 0.0),
1841        PipelineId::dummy(),
1842        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid),
1843    );
1844
1845    let root_scroll = st.add_scroll_frame(
1846        root,
1847        ExternalScrollId(1, PipelineId::dummy()),
1848        PipelineId::dummy(),
1849        &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1850        &LayoutSize::new(400.0, 400.0),
1851        ScrollFrameKind::Explicit,
1852        LayoutVector2D::new(0.0, 0.0),
1853        APZScrollGeneration::default(),
1854        HasScrollLinkedEffect::No,
1855        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid),
1856    );
1857
1858    let sub_scroll = st.add_scroll_frame(
1859        root_scroll,
1860        ExternalScrollId(1, PipelineId::dummy()),
1861        PipelineId::dummy(),
1862        &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1863        &LayoutSize::new(800.0, 400.0),
1864        ScrollFrameKind::Explicit,
1865        LayoutVector2D::new(0.0, 0.0),
1866        APZScrollGeneration::default(),
1867        HasScrollLinkedEffect::No,
1868        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid),
1869    );
1870
1871    assert_eq!(st.find_scroll_root(sub_scroll, true), sub_scroll);
1872}
1873
1874/// Tests that we select the sub scroll frame when the root scroll frame is too small.
1875#[test]
1876fn test_find_scroll_root_too_small() {
1877    let mut st = SceneSpatialTree::new();
1878    let pid = PipelineInstanceId::new(0);
1879
1880    let root = st.add_reference_frame(
1881        st.root_reference_frame_index(),
1882        TransformStyle::Flat,
1883        PropertyBinding::Value(LayoutTransform::identity()),
1884        ReferenceFrameKind::Transform {
1885            is_2d_scale_translation: true,
1886            should_snap: true,
1887            paired_with_perspective: false,
1888        },
1889        LayoutVector2D::new(0.0, 0.0),
1890        PipelineId::dummy(),
1891        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid),
1892    );
1893
1894    let root_scroll = st.add_scroll_frame(
1895        root,
1896        ExternalScrollId(1, PipelineId::dummy()),
1897        PipelineId::dummy(),
1898        &LayoutRect::from_size(LayoutSize::new(MIN_SCROLL_ROOT_SIZE, MIN_SCROLL_ROOT_SIZE)),
1899        &LayoutSize::new(1000.0, 1000.0),
1900        ScrollFrameKind::Explicit,
1901        LayoutVector2D::new(0.0, 0.0),
1902        APZScrollGeneration::default(),
1903        HasScrollLinkedEffect::No,
1904        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid),
1905    );
1906
1907    let sub_scroll = st.add_scroll_frame(
1908        root_scroll,
1909        ExternalScrollId(1, PipelineId::dummy()),
1910        PipelineId::dummy(),
1911        &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1912        &LayoutSize::new(800.0, 400.0),
1913        ScrollFrameKind::Explicit,
1914        LayoutVector2D::new(0.0, 0.0),
1915        APZScrollGeneration::default(),
1916        HasScrollLinkedEffect::No,
1917        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid),
1918    );
1919
1920    assert_eq!(st.find_scroll_root(sub_scroll, true), sub_scroll);
1921}
1922
1923/// Tests that we select the root scroll node, even if it is not scrollable,
1924/// when encountering a non-axis-aligned transform.
1925#[test]
1926fn test_find_scroll_root_perspective() {
1927    let mut st = SceneSpatialTree::new();
1928    let pid = PipelineInstanceId::new(0);
1929
1930    let root = st.add_reference_frame(
1931        st.root_reference_frame_index(),
1932        TransformStyle::Flat,
1933        PropertyBinding::Value(LayoutTransform::identity()),
1934        ReferenceFrameKind::Transform {
1935            is_2d_scale_translation: true,
1936            should_snap: true,
1937            paired_with_perspective: false,
1938        },
1939        LayoutVector2D::new(0.0, 0.0),
1940        PipelineId::dummy(),
1941        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid),
1942    );
1943
1944    let root_scroll = st.add_scroll_frame(
1945        root,
1946        ExternalScrollId(1, PipelineId::dummy()),
1947        PipelineId::dummy(),
1948        &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1949        &LayoutSize::new(400.0, 400.0),
1950        ScrollFrameKind::Explicit,
1951        LayoutVector2D::new(0.0, 0.0),
1952        APZScrollGeneration::default(),
1953        HasScrollLinkedEffect::No,
1954        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid),
1955    );
1956
1957    let perspective = st.add_reference_frame(
1958        root_scroll,
1959        TransformStyle::Flat,
1960        PropertyBinding::Value(LayoutTransform::identity()),
1961        ReferenceFrameKind::Perspective {
1962            scrolling_relative_to: None,
1963        },
1964        LayoutVector2D::new(0.0, 0.0),
1965        PipelineId::dummy(),
1966        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid),
1967    );
1968
1969    let sub_scroll = st.add_scroll_frame(
1970        perspective,
1971        ExternalScrollId(1, PipelineId::dummy()),
1972        PipelineId::dummy(),
1973        &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1974        &LayoutSize::new(800.0, 400.0),
1975        ScrollFrameKind::Explicit,
1976        LayoutVector2D::new(0.0, 0.0),
1977        APZScrollGeneration::default(),
1978        HasScrollLinkedEffect::No,
1979        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3), PipelineId::dummy(), pid),
1980    );
1981
1982    assert_eq!(st.find_scroll_root(sub_scroll, true), root_scroll);
1983}
1984
1985/// Tests that encountering a 2D scale or translation transform does not prevent
1986/// us from selecting the sub scroll frame if the root scroll frame is unscrollable.
1987#[test]
1988fn test_find_scroll_root_2d_scale() {
1989    let mut st = SceneSpatialTree::new();
1990    let pid = PipelineInstanceId::new(0);
1991
1992    let root = st.add_reference_frame(
1993        st.root_reference_frame_index(),
1994        TransformStyle::Flat,
1995        PropertyBinding::Value(LayoutTransform::identity()),
1996        ReferenceFrameKind::Transform {
1997            is_2d_scale_translation: true,
1998            should_snap: true,
1999            paired_with_perspective: false,
2000        },
2001        LayoutVector2D::new(0.0, 0.0),
2002        PipelineId::dummy(),
2003        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid),
2004    );
2005
2006    let root_scroll = st.add_scroll_frame(
2007        root,
2008        ExternalScrollId(1, PipelineId::dummy()),
2009        PipelineId::dummy(),
2010        &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
2011        &LayoutSize::new(400.0, 400.0),
2012        ScrollFrameKind::Explicit,
2013        LayoutVector2D::new(0.0, 0.0),
2014        APZScrollGeneration::default(),
2015        HasScrollLinkedEffect::No,
2016        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid),
2017    );
2018
2019    let scale = st.add_reference_frame(
2020        root_scroll,
2021        TransformStyle::Flat,
2022        PropertyBinding::Value(LayoutTransform::identity()),
2023        ReferenceFrameKind::Transform {
2024            is_2d_scale_translation: true,
2025            should_snap: false,
2026            paired_with_perspective: false,
2027        },
2028        LayoutVector2D::new(0.0, 0.0),
2029        PipelineId::dummy(),
2030        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid),
2031    );
2032
2033    let sub_scroll = st.add_scroll_frame(
2034        scale,
2035        ExternalScrollId(1, PipelineId::dummy()),
2036        PipelineId::dummy(),
2037        &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
2038        &LayoutSize::new(800.0, 400.0),
2039        ScrollFrameKind::Explicit,
2040        LayoutVector2D::new(0.0, 0.0),
2041        APZScrollGeneration::default(),
2042        HasScrollLinkedEffect::No,
2043        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3), PipelineId::dummy(), pid),
2044    );
2045
2046    assert_eq!(st.find_scroll_root(sub_scroll, true), sub_scroll);
2047}
2048
2049/// Tests that a sticky spatial node is chosen as the scroll root rather than
2050/// its parent scroll frame
2051#[test]
2052fn test_find_scroll_root_sticky() {
2053    let mut st = SceneSpatialTree::new();
2054    let pid = PipelineInstanceId::new(0);
2055
2056    let root = st.add_reference_frame(
2057        st.root_reference_frame_index(),
2058        TransformStyle::Flat,
2059        PropertyBinding::Value(LayoutTransform::identity()),
2060        ReferenceFrameKind::Transform {
2061            is_2d_scale_translation: true,
2062            should_snap: true,
2063            paired_with_perspective: false,
2064        },
2065        LayoutVector2D::new(0.0, 0.0),
2066        PipelineId::dummy(),
2067        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid),
2068    );
2069
2070    let scroll = st.add_scroll_frame(
2071        root,
2072        ExternalScrollId(1, PipelineId::dummy()),
2073        PipelineId::dummy(),
2074        &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
2075        &LayoutSize::new(400.0, 800.0),
2076        ScrollFrameKind::Explicit,
2077        LayoutVector2D::new(0.0, 0.0),
2078        APZScrollGeneration::default(),
2079        HasScrollLinkedEffect::No,
2080        SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid),
2081    );
2082
2083    let sticky = st.add_sticky_frame(
2084        scroll,
2085        StickyFrameInfo {
2086            frame_rect: LayoutRect::from_size(LayoutSize::new(400.0, 100.0)),
2087            margins: euclid::SideOffsets2D::new(Some(0.0), None, None, None),
2088            vertical_offset_bounds: api::StickyOffsetBounds::new(0.0, 0.0),
2089            horizontal_offset_bounds: api::StickyOffsetBounds::new(0.0, 0.0),
2090            previously_applied_offset: LayoutVector2D::zero(),
2091            current_offset: LayoutVector2D::zero(),
2092            transform: None
2093        },
2094        PipelineId::dummy(),
2095        SpatialTreeItemKey::new(0, 2),
2096        pid,
2097    );
2098
2099    assert_eq!(st.find_scroll_root(sticky, true), sticky);
2100    assert_eq!(st.find_scroll_root(sticky, false), scroll);
2101}
2102
2103#[test]
2104fn test_world_transforms() {
2105  // Create a spatial tree with a scroll frame node with scroll offset (0, 200).
2106  let mut cst = SceneSpatialTree::new();
2107  let pid = PipelineInstanceId::new(0);
2108  let scroll = cst.add_scroll_frame(
2109      cst.root_reference_frame_index(),
2110      ExternalScrollId(1, PipelineId::dummy()),
2111      PipelineId::dummy(),
2112      &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
2113      &LayoutSize::new(400.0, 800.0),
2114      ScrollFrameKind::Explicit, 
2115      LayoutVector2D::new(0.0, 200.0),
2116      APZScrollGeneration::default(),
2117      HasScrollLinkedEffect::No,
2118      SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid));
2119
2120  let mut st = SpatialTree::new();
2121  st.apply_updates(cst.end_frame_and_get_pending_updates());
2122  st.update_tree(&SceneProperties::new());
2123
2124  // The node's world transform should reflect the scroll offset,
2125  // e.g. here it should be (0, -200) to reflect that the content has been
2126  // scrolled up by 200px.
2127  assert_eq!(
2128      st.get_world_transform(scroll).into_transform(),
2129      LayoutToWorldTransform::translation(0.0, -200.0, 0.0));
2130
2131  // The node's world viewport transform only reflects enclosing scrolling
2132  // or transforms. Here we don't have any, so it should be the identity.
2133  assert_eq!(
2134      st.get_world_viewport_transform(scroll).into_transform(),
2135      LayoutToWorldTransform::identity());
2136}
2137
2138/// Tests that a spatial node that is async zooming and all of its descendants
2139/// are correctly marked as having themselves an ancestor that is zooming.
2140#[test]
2141fn test_is_ancestor_or_self_zooming() {
2142    let mut cst = SceneSpatialTree::new();
2143    let root_reference_frame_index = cst.root_reference_frame_index();
2144
2145    let root = add_reference_frame(
2146        &mut cst,
2147        root_reference_frame_index,
2148        LayoutTransform::identity(),
2149        LayoutVector2D::zero(),
2150        SpatialTreeItemKey::new(0, 0),
2151    );
2152    let child1 = add_reference_frame(
2153        &mut cst,
2154        root,
2155        LayoutTransform::identity(),
2156        LayoutVector2D::zero(),
2157        SpatialTreeItemKey::new(0, 1),
2158    );
2159    let child2 = add_reference_frame(
2160        &mut cst,
2161        child1,
2162        LayoutTransform::identity(),
2163        LayoutVector2D::zero(),
2164        SpatialTreeItemKey::new(0, 2),
2165    );
2166
2167    let mut st = SpatialTree::new();
2168    st.apply_updates(cst.end_frame_and_get_pending_updates());
2169
2170    // Mark the root node as async zooming
2171    st.get_spatial_node_mut(root).is_async_zooming = true;
2172    st.update_tree(&SceneProperties::new());
2173
2174    // Ensure that the root node and all descendants are marked as having
2175    // themselves or an ancestor zooming
2176    assert!(st.get_spatial_node(root).is_ancestor_or_self_zooming);
2177    assert!(st.get_spatial_node(child1).is_ancestor_or_self_zooming);
2178    assert!(st.get_spatial_node(child2).is_ancestor_or_self_zooming);
2179}