1use std::cell::Cell;
8use std::collections::HashMap;
9
10use base::id::ScrollTreeNodeId;
11use base::print_tree::PrintTree;
12use bitflags::bitflags;
13use embedder_traits::ViewportDetails;
14use euclid::SideOffsets2D;
15use fnv::FnvHashMap;
16use malloc_size_of_derive::MallocSizeOf;
17use serde::{Deserialize, Serialize};
18use servo_geometry::FastLayoutTransform;
19use style::values::specified::Overflow;
20use webrender_api::units::{LayoutPixel, LayoutPoint, LayoutRect, LayoutSize, LayoutVector2D};
21use webrender_api::{
22 Epoch, ExternalScrollId, PipelineId, ReferenceFrameKind, ScrollLocation, SpatialId,
23 StickyOffsetBounds, TransformStyle,
24};
25
26#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
30pub struct ScrollType(u8);
31
32bitflags! {
33 impl ScrollType: u8 {
34 const InputEvents = 1 << 0;
37 const Script = 1 << 1;
39 }
40}
41
42impl From<Overflow> for ScrollType {
44 fn from(overflow: Overflow) -> Self {
45 match overflow {
46 Overflow::Hidden => ScrollType::Script,
47 Overflow::Scroll | Overflow::Auto => ScrollType::Script | ScrollType::InputEvents,
48 Overflow::Visible | Overflow::Clip => ScrollType::empty(),
49 }
50 }
51}
52
53#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
55pub struct AxesScrollSensitivity {
56 pub x: ScrollType,
57 pub y: ScrollType,
58}
59
60#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
61pub enum SpatialTreeNodeInfo {
62 ReferenceFrame(ReferenceFrameNodeInfo),
63 Scroll(ScrollableNodeInfo),
64 Sticky(StickyNodeInfo),
65}
66
67#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
68pub struct StickyNodeInfo {
69 pub frame_rect: LayoutRect,
70 pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
71 pub vertical_offset_bounds: StickyOffsetBounds,
72 pub horizontal_offset_bounds: StickyOffsetBounds,
73}
74
75impl StickyNodeInfo {
76 fn calculate_sticky_offset(&self, ancestor_sticky_info: &AncestorStickyInfo) -> LayoutVector2D {
81 let viewport_scroll_offset = &ancestor_sticky_info.nearest_scrolling_ancestor_offset;
82 let viewport_rect = &ancestor_sticky_info.nearest_scrolling_ancestor_viewport;
83
84 if self.margins.top.is_none() &&
85 self.margins.bottom.is_none() &&
86 self.margins.left.is_none() &&
87 self.margins.right.is_none()
88 {
89 return LayoutVector2D::zero();
90 }
91
92 let mut sticky_rect = self.frame_rect.translate(*viewport_scroll_offset);
98
99 let mut sticky_offset = LayoutVector2D::zero();
100 if let Some(margin) = self.margins.top {
101 let top_viewport_edge = viewport_rect.min.y + margin;
102 if sticky_rect.min.y < top_viewport_edge {
103 sticky_offset.y = top_viewport_edge - sticky_rect.min.y;
106 }
107 }
108
109 if sticky_offset.y <= 0.0 {
114 if let Some(margin) = self.margins.bottom {
115 sticky_rect.min.y += sticky_offset.y;
121 sticky_rect.max.y += sticky_offset.y;
122
123 let bottom_viewport_edge = viewport_rect.max.y - margin;
128 if sticky_rect.max.y > bottom_viewport_edge {
129 sticky_offset.y += bottom_viewport_edge - sticky_rect.max.y;
130 }
131 }
132 }
133
134 if let Some(margin) = self.margins.left {
136 let left_viewport_edge = viewport_rect.min.x + margin;
137 if sticky_rect.min.x < left_viewport_edge {
138 sticky_offset.x = left_viewport_edge - sticky_rect.min.x;
139 }
140 }
141
142 if sticky_offset.x <= 0.0 {
143 if let Some(margin) = self.margins.right {
144 sticky_rect.min.x += sticky_offset.x;
145 sticky_rect.max.x += sticky_offset.x;
146 let right_viewport_edge = viewport_rect.max.x - margin;
147 if sticky_rect.max.x > right_viewport_edge {
148 sticky_offset.x += right_viewport_edge - sticky_rect.max.x;
149 }
150 }
151 }
152
153 let clamp =
156 |value: f32, bounds: &StickyOffsetBounds| (value).max(bounds.min).min(bounds.max);
157 sticky_offset.y = clamp(sticky_offset.y, &self.vertical_offset_bounds);
158 sticky_offset.x = clamp(sticky_offset.x, &self.horizontal_offset_bounds);
159
160 sticky_offset
161 }
162}
163
164#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
165pub struct ReferenceFrameNodeInfo {
166 pub origin: LayoutPoint,
167 pub frame_origin_for_query: LayoutPoint,
169 pub transform_style: TransformStyle,
170 pub transform: FastLayoutTransform,
171 pub kind: ReferenceFrameKind,
172}
173
174#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
177pub struct ScrollableNodeInfo {
178 pub external_id: ExternalScrollId,
181
182 pub content_rect: LayoutRect,
184
185 pub clip_rect: LayoutRect,
187
188 pub scroll_sensitivity: AxesScrollSensitivity,
190
191 pub offset: LayoutVector2D,
193
194 pub offset_changed: Cell<bool>,
197}
198
199impl ScrollableNodeInfo {
200 fn scroll_to_offset(
201 &mut self,
202 new_offset: LayoutVector2D,
203 context: ScrollType,
204 ) -> Option<LayoutVector2D> {
205 if !self.scroll_sensitivity.x.contains(context) &&
206 !self.scroll_sensitivity.y.contains(context)
207 {
208 return None;
209 }
210
211 let scrollable_size = self.scrollable_size();
212 let original_layer_scroll_offset = self.offset;
213
214 if scrollable_size.width > 0. && self.scroll_sensitivity.x.contains(context) {
215 self.offset.x = new_offset.x.clamp(0.0, scrollable_size.width);
216 }
217
218 if scrollable_size.height > 0. && self.scroll_sensitivity.y.contains(context) {
219 self.offset.y = new_offset.y.clamp(0.0, scrollable_size.height);
220 }
221
222 if self.offset != original_layer_scroll_offset {
223 self.offset_changed.set(true);
224 Some(self.offset)
225 } else {
226 None
227 }
228 }
229
230 fn scroll_to_webrender_location(
231 &mut self,
232 scroll_location: ScrollLocation,
233 context: ScrollType,
234 ) -> Option<LayoutVector2D> {
235 if !self.scroll_sensitivity.x.contains(context) &&
236 !self.scroll_sensitivity.y.contains(context)
237 {
238 return None;
239 }
240
241 let delta = match scroll_location {
242 ScrollLocation::Delta(delta) => delta,
243 ScrollLocation::Start => {
244 if self.offset.y.round() <= 0.0 {
245 return None;
247 }
248
249 self.offset.y = 0.0;
250 self.offset_changed.set(true);
251 return Some(self.offset);
252 },
253 ScrollLocation::End => {
254 let end_pos = self.scrollable_size().height;
255 if self.offset.y.round() >= end_pos {
256 return None;
258 }
259
260 self.offset.y = end_pos;
261 self.offset_changed.set(true);
262 return Some(self.offset);
263 },
264 };
265
266 self.scroll_to_offset(self.offset + delta, context)
267 }
268}
269
270impl ScrollableNodeInfo {
271 fn scrollable_size(&self) -> LayoutSize {
272 self.content_rect.size() - self.clip_rect.size()
273 }
274}
275
276#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, Serialize)]
283pub struct ScrollTreeNodeTransformationCache {
284 node_to_root_transform: FastLayoutTransform,
285 root_to_node_transform: Option<FastLayoutTransform>,
286}
287
288#[derive(Default)]
289struct AncestorStickyInfo {
290 nearest_scrolling_ancestor_offset: LayoutVector2D,
291 nearest_scrolling_ancestor_viewport: LayoutRect,
292}
293
294#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
295pub struct ScrollTreeNode {
298 pub parent: Option<ScrollTreeNodeId>,
301
302 pub children: Vec<ScrollTreeNodeId>,
304
305 pub webrender_id: Option<SpatialId>,
308
309 pub info: SpatialTreeNodeInfo,
312
313 transformation_cache: Cell<Option<ScrollTreeNodeTransformationCache>>,
316}
317
318impl ScrollTreeNode {
319 pub fn webrender_id(&self) -> SpatialId {
322 self.webrender_id
323 .expect("Should have called ScrollTree::build_display_list before querying SpatialId")
324 }
325
326 pub fn external_id(&self) -> Option<ExternalScrollId> {
328 match self.info {
329 SpatialTreeNodeInfo::Scroll(ref info) => Some(info.external_id),
330 _ => None,
331 }
332 }
333
334 pub fn offset(&self) -> Option<LayoutVector2D> {
336 match self.info {
337 SpatialTreeNodeInfo::Scroll(ref info) => Some(info.offset),
338 _ => None,
339 }
340 }
341
342 fn scroll(
346 &mut self,
347 scroll_location: ScrollLocation,
348 context: ScrollType,
349 ) -> Option<(ExternalScrollId, LayoutVector2D)> {
350 let SpatialTreeNodeInfo::Scroll(ref mut info) = self.info else {
351 return None;
352 };
353
354 info.scroll_to_webrender_location(scroll_location, context)
355 .map(|location| (info.external_id, location))
356 }
357
358 pub fn debug_print(&self, print_tree: &mut PrintTree, node_index: usize) {
359 match &self.info {
360 SpatialTreeNodeInfo::ReferenceFrame(info) => {
361 print_tree.new_level(format!(
362 "Reference Frame({node_index}): webrender_id={:?}\
363 \norigin: {:?}\
364 \ntransform_style: {:?}\
365 \ntransform: {:?}\
366 \nkind: {:?}",
367 self.webrender_id, info.origin, info.transform_style, info.transform, info.kind,
368 ));
369 },
370 SpatialTreeNodeInfo::Scroll(info) => {
371 print_tree.new_level(format!(
372 "Scroll Frame({node_index}): webrender_id={:?}\
373 \nexternal_id: {:?}\
374 \ncontent_rect: {:?}\
375 \nclip_rect: {:?}\
376 \nscroll_sensitivity: {:?}\
377 \noffset: {:?}",
378 self.webrender_id,
379 info.external_id,
380 info.content_rect,
381 info.clip_rect,
382 info.scroll_sensitivity,
383 info.offset,
384 ));
385 },
386 SpatialTreeNodeInfo::Sticky(info) => {
387 print_tree.new_level(format!(
388 "Sticky Frame({node_index}): webrender_id={:?}\
389 \nframe_rect: {:?}\
390 \nmargins: {:?}\
391 \nhorizontal_offset_bounds: {:?}\
392 \nvertical_offset_bounds: {:?}",
393 self.webrender_id,
394 info.frame_rect,
395 info.margins,
396 info.horizontal_offset_bounds,
397 info.vertical_offset_bounds,
398 ));
399 },
400 };
401 }
402
403 fn invalidate_cached_transforms(&self, scroll_tree: &ScrollTree, ancestors_invalid: bool) {
404 let node_invalid = match &self.info {
405 SpatialTreeNodeInfo::Scroll(info) => info.offset_changed.take(),
406 _ => false,
407 };
408
409 let invalid = node_invalid || ancestors_invalid;
410 if invalid {
411 self.transformation_cache.set(None);
412 }
413
414 for child_id in &self.children {
415 scroll_tree
416 .get_node(child_id)
417 .invalidate_cached_transforms(scroll_tree, invalid);
418 }
419 }
420}
421
422#[derive(Debug, Default, Deserialize, MallocSizeOf, Serialize)]
426pub struct ScrollTree {
427 pub nodes: Vec<ScrollTreeNode>,
431}
432
433impl ScrollTree {
434 pub fn add_scroll_tree_node(
436 &mut self,
437 parent: Option<&ScrollTreeNodeId>,
438 info: SpatialTreeNodeInfo,
439 ) -> ScrollTreeNodeId {
440 self.nodes.push(ScrollTreeNode {
441 parent: parent.cloned(),
442 children: Vec::new(),
443 webrender_id: None,
444 info,
445 transformation_cache: Cell::default(),
446 });
447
448 let new_node_id = ScrollTreeNodeId {
449 index: self.nodes.len() - 1,
450 };
451
452 if let Some(parent_id) = parent {
453 self.get_node_mut(parent_id).children.push(new_node_id);
454 }
455
456 new_node_id
457 }
458
459 pub fn update_mapping(&mut self, mapping: Vec<SpatialId>) {
462 for (spatial_id, node) in mapping.into_iter().zip(self.nodes.iter_mut()) {
463 node.webrender_id = Some(spatial_id);
464 }
465 }
466
467 pub fn get_node_mut(&mut self, id: &ScrollTreeNodeId) -> &mut ScrollTreeNode {
469 &mut self.nodes[id.index]
470 }
471
472 pub fn get_node(&self, id: &ScrollTreeNodeId) -> &ScrollTreeNode {
474 &self.nodes[id.index]
475 }
476
477 pub fn webrender_id(&self, id: &ScrollTreeNodeId) -> SpatialId {
480 self.get_node(id).webrender_id()
481 }
482
483 pub fn scroll_node_or_ancestor_inner(
484 &mut self,
485 scroll_node_id: &ScrollTreeNodeId,
486 scroll_location: ScrollLocation,
487 context: ScrollType,
488 ) -> Option<(ExternalScrollId, LayoutVector2D)> {
489 let parent = {
490 let node = &mut self.get_node_mut(scroll_node_id);
491 let result = node.scroll(scroll_location, context);
492 if result.is_some() {
493 return result;
494 }
495 node.parent
496 };
497
498 parent.and_then(|parent| {
499 self.scroll_node_or_ancestor_inner(&parent, scroll_location, context)
500 })
501 }
502
503 fn node_with_external_scroll_node_id(
504 &self,
505 external_id: &ExternalScrollId,
506 ) -> Option<ScrollTreeNodeId> {
507 self.nodes
508 .iter()
509 .enumerate()
510 .find_map(|(index, node)| match &node.info {
511 SpatialTreeNodeInfo::Scroll(info) if info.external_id == *external_id => {
512 Some(ScrollTreeNodeId { index })
513 },
514 _ => None,
515 })
516 }
517
518 pub fn scroll_node_or_ancestor(
523 &mut self,
524 external_id: &ExternalScrollId,
525 scroll_location: ScrollLocation,
526 context: ScrollType,
527 ) -> Option<(ExternalScrollId, LayoutVector2D)> {
528 let scroll_node_id = self.node_with_external_scroll_node_id(external_id)?;
529 let result = self.scroll_node_or_ancestor_inner(&scroll_node_id, scroll_location, context);
530 if result.is_some() {
531 self.invalidate_cached_transforms();
532 }
533 result
534 }
535
536 pub fn set_scroll_offset_for_node_with_external_scroll_id(
539 &mut self,
540 external_scroll_id: ExternalScrollId,
541 offset: LayoutVector2D,
542 context: ScrollType,
543 ) -> Option<LayoutVector2D> {
544 let result = self.nodes.iter_mut().find_map(|node| match node.info {
545 SpatialTreeNodeInfo::Scroll(ref mut scroll_info)
546 if scroll_info.external_id == external_scroll_id =>
547 {
548 scroll_info.scroll_to_offset(offset, context)
549 },
550 _ => None,
551 });
552
553 if result.is_some() {
554 self.invalidate_cached_transforms();
555 }
556
557 result
558 }
559
560 pub fn set_all_scroll_offsets(
563 &mut self,
564 offsets: &FnvHashMap<ExternalScrollId, LayoutVector2D>,
565 ) {
566 for node in self.nodes.iter_mut() {
567 if let SpatialTreeNodeInfo::Scroll(ref mut scroll_info) = node.info {
568 if let Some(offset) = offsets.get(&scroll_info.external_id) {
569 scroll_info.scroll_to_offset(*offset, ScrollType::Script);
570 }
571 }
572 }
573
574 self.invalidate_cached_transforms();
575 }
576
577 pub fn reset_all_scroll_offsets(&mut self) {
579 for node in self.nodes.iter_mut() {
580 if let SpatialTreeNodeInfo::Scroll(ref mut scroll_info) = node.info {
581 scroll_info.scroll_to_offset(LayoutVector2D::zero(), ScrollType::Script);
582 }
583 }
584
585 self.invalidate_cached_transforms();
586 }
587
588 pub fn scroll_offsets(&self) -> FnvHashMap<ExternalScrollId, LayoutVector2D> {
591 HashMap::from_iter(self.nodes.iter().filter_map(|node| match node.info {
592 SpatialTreeNodeInfo::Scroll(ref scroll_info) => {
593 Some((scroll_info.external_id, scroll_info.offset))
594 },
595 _ => None,
596 }))
597 }
598
599 pub fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D> {
602 self.nodes.iter().find_map(|node| match node.info {
603 SpatialTreeNodeInfo::Scroll(ref info) if info.external_id == id => Some(info.offset),
604 _ => None,
605 })
606 }
607
608 pub fn cumulative_node_to_root_transform(
611 &self,
612 node_id: &ScrollTreeNodeId,
613 ) -> FastLayoutTransform {
614 let node = self.get_node(node_id);
615 if let Some(cached_transforms) = node.transformation_cache.get() {
616 return cached_transforms.node_to_root_transform;
617 }
618
619 let (transforms, _) = self.cumulative_node_transform_inner(node);
620 node.transformation_cache.set(Some(transforms));
621 transforms.node_to_root_transform
622 }
623
624 pub fn cumulative_root_to_node_transform(
628 &self,
629 node_id: &ScrollTreeNodeId,
630 ) -> Option<FastLayoutTransform> {
631 let node = self.get_node(node_id);
632 if let Some(cached_transforms) = node.transformation_cache.get() {
633 return cached_transforms.root_to_node_transform;
634 }
635
636 let (transforms, _) = self.cumulative_node_transform_inner(node);
637 node.transformation_cache.set(Some(transforms));
638 transforms.root_to_node_transform
639 }
640
641 fn cumulative_node_transform_inner(
643 &self,
644 node: &ScrollTreeNode,
645 ) -> (ScrollTreeNodeTransformationCache, AncestorStickyInfo) {
646 let (parent_transforms, mut sticky_info) = match node.parent {
647 Some(parent_id) => self.cumulative_node_transform_inner(self.get_node(&parent_id)),
648 None => (Default::default(), Default::default()),
649 };
650
651 let (node_to_parent_transform, parent_to_node_transform) = match &node.info {
652 SpatialTreeNodeInfo::ReferenceFrame(info) => {
653 let offset = info.frame_origin_for_query.to_vector();
656 let node_to_parent_transform =
657 info.transform.pre_translate(-offset).then_translate(offset);
658
659 let parent_to_node_transform =
660 FastLayoutTransform::Offset(-info.origin.to_vector());
661 let parent_to_node_transform = info
662 .transform
663 .inverse()
664 .map(|inverse_transform| parent_to_node_transform.then(&inverse_transform));
665
666 sticky_info.nearest_scrolling_ancestor_viewport = sticky_info
667 .nearest_scrolling_ancestor_viewport
668 .translate(-info.origin.to_vector());
669
670 (node_to_parent_transform, parent_to_node_transform)
671 },
672 SpatialTreeNodeInfo::Scroll(info) => {
673 sticky_info.nearest_scrolling_ancestor_viewport = info.clip_rect;
674 sticky_info.nearest_scrolling_ancestor_offset = -info.offset;
675 let offset_transform = FastLayoutTransform::Offset(-info.offset);
676 (offset_transform, offset_transform.inverse())
677 },
678
679 SpatialTreeNodeInfo::Sticky(info) => {
680 let offset = info.calculate_sticky_offset(&sticky_info);
681 sticky_info.nearest_scrolling_ancestor_offset += offset;
682 let offset_transform = FastLayoutTransform::Offset(offset);
683 (offset_transform, offset_transform.inverse())
684 },
685 };
686
687 let node_to_root_transform =
688 node_to_parent_transform.then(&parent_transforms.node_to_root_transform);
689 let root_to_node_transform = parent_to_node_transform.map(|parent_to_node_transform| {
690 parent_transforms
691 .root_to_node_transform
692 .map_or(parent_to_node_transform, |parent_transform| {
693 parent_transform.then(&parent_to_node_transform)
694 })
695 });
696
697 let transforms = ScrollTreeNodeTransformationCache {
698 node_to_root_transform,
699 root_to_node_transform,
700 };
701 (transforms, sticky_info)
702 }
703
704 fn invalidate_cached_transforms(&self) {
705 let Some(root_node) = self.nodes.first() else {
706 return;
707 };
708 root_node.invalidate_cached_transforms(self, false );
709 }
710
711 fn external_scroll_id_for_scroll_tree_node(
712 &self,
713 id: ScrollTreeNodeId,
714 ) -> Option<ExternalScrollId> {
715 let mut maybe_node = Some(self.get_node(&id));
716
717 while let Some(node) = maybe_node {
718 if let Some(external_scroll_id) = node.external_id() {
719 return Some(external_scroll_id);
720 }
721 maybe_node = node.parent.map(|id| self.get_node(&id));
722 }
723
724 None
725 }
726}
727
728type AdjacencyListForPrint = Vec<Vec<ScrollTreeNodeId>>;
734
735impl ScrollTree {
739 fn nodes_in_adjacency_list(&self) -> AdjacencyListForPrint {
740 let mut adjacency_list: AdjacencyListForPrint = vec![Default::default(); self.nodes.len()];
741
742 for (node_index, node) in self.nodes.iter().enumerate() {
743 let current_id = ScrollTreeNodeId { index: node_index };
744 if let Some(parent_id) = node.parent {
745 adjacency_list[parent_id.index].push(current_id);
746 }
747 }
748
749 adjacency_list
750 }
751
752 fn debug_print_traversal(
753 &self,
754 print_tree: &mut PrintTree,
755 current_id: &ScrollTreeNodeId,
756 adjacency_list: &[Vec<ScrollTreeNodeId>],
757 ) {
758 for node_id in &adjacency_list[current_id.index] {
759 self.nodes[node_id.index].debug_print(print_tree, node_id.index);
760 self.debug_print_traversal(print_tree, node_id, adjacency_list);
761 }
762 print_tree.end_level();
763 }
764
765 pub fn debug_print(&self) {
772 let mut print_tree = PrintTree::new("Scroll Tree".to_owned());
773
774 let adj_list = self.nodes_in_adjacency_list();
775 let root_id = ScrollTreeNodeId { index: 0 };
776
777 self.nodes[root_id.index].debug_print(&mut print_tree, root_id.index);
778 self.debug_print_traversal(&mut print_tree, &root_id, &adj_list);
779 print_tree.end_level();
780 }
781}
782
783#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
786pub struct CompositorDisplayListInfo {
787 pub pipeline_id: PipelineId,
789
790 pub viewport_details: ViewportDetails,
793
794 pub content_size: LayoutSize,
796
797 pub epoch: Epoch,
799
800 pub scroll_tree: ScrollTree,
803
804 pub root_reference_frame_id: ScrollTreeNodeId,
807
808 pub root_scroll_node_id: ScrollTreeNodeId,
811
812 pub is_contentful: bool,
816
817 pub first_reflow: bool,
820}
821
822impl CompositorDisplayListInfo {
823 pub fn new(
826 viewport_details: ViewportDetails,
827 content_size: LayoutSize,
828 pipeline_id: PipelineId,
829 epoch: Epoch,
830 viewport_scroll_sensitivity: AxesScrollSensitivity,
831 first_reflow: bool,
832 ) -> Self {
833 let mut scroll_tree = ScrollTree::default();
834 let root_reference_frame_id = scroll_tree.add_scroll_tree_node(
835 None,
836 SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo {
837 origin: Default::default(),
838 frame_origin_for_query: Default::default(),
839 transform_style: TransformStyle::Flat,
840 transform: FastLayoutTransform::identity(),
841 kind: ReferenceFrameKind::default(),
842 }),
843 );
844 let root_scroll_node_id = scroll_tree.add_scroll_tree_node(
845 Some(&root_reference_frame_id),
846 SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo {
847 external_id: ExternalScrollId(0, pipeline_id),
848 content_rect: LayoutRect::from_origin_and_size(LayoutPoint::zero(), content_size),
849 clip_rect: LayoutRect::from_origin_and_size(
850 LayoutPoint::zero(),
851 viewport_details.layout_size(),
852 ),
853 scroll_sensitivity: viewport_scroll_sensitivity,
854 offset: LayoutVector2D::zero(),
855 offset_changed: Cell::new(false),
856 }),
857 );
858
859 CompositorDisplayListInfo {
860 pipeline_id,
861 viewport_details,
862 content_size,
863 epoch,
864 scroll_tree,
865 root_reference_frame_id,
866 root_scroll_node_id,
867 is_contentful: false,
868 first_reflow,
869 }
870 }
871
872 pub fn external_scroll_id_for_scroll_tree_node(
873 &self,
874 id: ScrollTreeNodeId,
875 ) -> ExternalScrollId {
876 self.scroll_tree
877 .external_scroll_id_for_scroll_tree_node(id)
878 .unwrap_or(ExternalScrollId(0, self.pipeline_id))
879 }
880}