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