tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

spatial_tree.rs (80601B)


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